Compare commits

...

57 Commits

Author SHA1 Message Date
Rhet Turnbull
3a1a4ad991 Release v0.60.5 (#1103) 2023-06-24 10:53:28 -07:00
Rhet Turnbull
2190628e82 Implemented --count, #1098 (#1102) 2023-06-24 10:50:34 -07:00
Rhet Turnbull
bb8e164f21 Unicode refactor (#1101)
* Began refactoring for improving unicode handling

* Added platform and unicode modules

* Added tests for unicode utilities

* Added tests for unicode utilities

* Added tests for unicode utilities

* Added tests for unicode utilities

* Fixed unicode tests for linux

* Fixed unicode tests for linux

* Fixed duplicate alubm name with --add-to-album

* Fixed test for linux

* Fix for duplicate unicode kewyords, see #907, #1085
2023-06-24 10:50:10 -07:00
Rhet Turnbull
7ccfe26e37 Added note on pipx reinstall: 2023-06-18 16:26:39 -07:00
Rhet Turnbull
2c80226ec8 fixed formatting (#1096) 2023-06-18 16:22:46 -07:00
Rhet Turnbull
492e1edb7f Updated CHANGELOG.md [skip ci] 2023-06-18 16:18:37 -07:00
Rhet Turnbull
72c6a165c7 Release v0.60.4 (#1095)
* Updated tested OS versions

* Version bump

* Release v0.60.4
2023-06-18 16:13:29 -07:00
Rhet Turnbull
e9aa289e8a Updated CHANGELOG.md [skip ci] 2023-06-18 10:03:57 -07:00
Rhet Turnbull
1fd73e5351 Release v0.60.3 (#1094) 2023-06-18 09:56:07 -07:00
Rhet Turnbull
8de871ccf1 Feature syndicated photos 1054 (#1093)
* Added find() to repl

* Added find() to repl

* Added support for syndicated photos #1054
2023-06-18 09:54:04 -07:00
allcontributors[bot]
0473b45631 add ces3001 as a contributor for bug (#1091)
* update README.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-06-17 07:38:06 -07:00
allcontributors[bot]
6b541b83ad add MaxLyt as a contributor for bug (#1090)
* update README.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-06-17 07:30:28 -07:00
Rhet Turnbull
633f3a92fa Updated CHANGELOG.md [skip ci] 2023-06-17 07:29:32 -07:00
Rhet Turnbull
de0b4fa11f Release v0.60.2 (#1089) 2023-06-17 07:18:55 -07:00
Rhet Turnbull
269a2dfa9e Fix download missing when not needed 1086 (#1088)
* Fixed staging to not call download missing if not needed

* Optimizations for #1086

* Memoize compiled XMP template, #1086
2023-06-17 07:14:24 -07:00
Rhet Turnbull
986010ec5f Updated example [skip ci] 2023-06-15 22:11:35 -07:00
Rhet Turnbull
e062582d14 Added export_preview.py example [skip_ci] 2023-05-31 21:41:35 -07:00
allcontributors[bot]
d30314bedc add rajscode as a contributor for doc (#1076)
* update README.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-05-14 20:26:39 -07:00
rajscode
7dfd24cb96 Update README.md (#1074)
since changed to single
2023-05-14 20:25:00 -07:00
Rhet Turnbull
fcb015dd4a Updated CHANGELOG.md [skip ci] 2023-05-14 08:36:27 -07:00
Rhet Turnbull
d2338992a2 Release 0.60.1 (#1073) 2023-05-14 08:29:49 -07:00
Rhet Turnbull
234db3fb54 Fix for #1071, crash with --dry-run (#1072) 2023-05-14 08:27:51 -07:00
Rhet Turnbull
55d4be6892 Updated CHANGELOG.md [skip ci] 2023-05-07 08:10:16 -07:00
Rhet Turnbull
922634ab3e Updated release v0.60.0 (#1067) 2023-05-07 07:59:51 -07:00
Rhet Turnbull
f0aa69d8bb Ran black (#1066) 2023-05-07 07:33:07 -07:00
Rhet Turnbull
ee8053d867 Fixed tests for ubuntu 2023-05-07 07:25:48 -07:00
Rhet Turnbull
253901281d Fixed requirements.txt for ubuntu 2023-05-07 07:14:18 -07:00
Rhet Turnbull
051da093ea Added platform conditionals to requirements.txt 2023-05-07 07:08:54 -07:00
Rhet Turnbull
fd5b16578c Added ubuntu runners 2023-05-07 07:02:45 -07:00
dvdkon
ca3da647f2 Port to non-MacOS platforms (#1026)
* Port to non-MacOS platforms

* Keep NFD normalization on macOS

* Update locale_util.py

Fix lint error from ruff (runs in CI)

* Update query.py

click.Option first arg needs to be a list (different than click.option)

* Dynamically normalize Unicode paths in test

* Fix missing import

---------

Co-authored-by: Rhet Turnbull <rturnbull@gmail.com>
2023-05-07 06:55:56 -07:00
Rhet Turnbull
0c85298c03 Updated API_README.md to add tables() 2023-04-16 09:19:10 -07:00
Rhet Turnbull
b4b58d3b00 Updated API_README.md to add tables() 2023-04-16 09:17:56 -07:00
Rhet Turnbull
ed543aa2d0 Feature phototables (#1059)
* Added tables() method to PhotoInfo to get access to underlying tables

* This time with the phototables code...
2023-04-16 09:02:59 -07:00
allcontributors[bot]
1b0c91db97 add pekingduck as a contributor for bug (#1058)
* update README.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-04-12 20:41:51 -07:00
Rhet Turnbull
dd3914328b Updated CHANGELOG.md [skip ci] 2023-04-10 20:59:09 -07:00
Rhet Turnbull
4b4252a73c Release 0.59.3 (#1053) 2023-04-10 20:49:19 -07:00
Rhet Turnbull
956cecfa30 Bug memory leak 1047 (#1052)
* Fix for memory leak, #1047

* Refactored photos_by_uuid, AlbumInfo.asdict for speed optimization

* Fix for huge crash log, #1048

* Fix for error on export #1046

* add rajscode as a contributor for bug (#1049)

* update README.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>

* add wernerzj as a contributor for bug (#1050)

* update README.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
Co-authored-by: Rhet Turnbull <rturnbull@gmail.com>

* Fixed all-contributors badge

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-04-10 16:50:17 -07:00
Rhet Turnbull
6f88f19950 Fixed all-contributors badge 2023-04-10 12:06:13 -07:00
allcontributors[bot]
ce4f3c4c0b add wernerzj as a contributor for bug (#1050)
* update README.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
Co-authored-by: Rhet Turnbull <rturnbull@gmail.com>
2023-04-10 12:02:37 -07:00
allcontributors[bot]
3993cf220d add rajscode as a contributor for bug (#1049)
* update README.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-04-10 11:57:10 -07:00
allcontributors[bot]
2ae41a5a7a add oPromessa as a contributor for doc (#1045)
* update README.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-04-08 21:58:26 -07:00
oPromessa
519a6b0035 Small typo on Development Environment creation instructions (#1043)
Corrections on READNE_DEV.md and  testes/README.md
2023-04-08 21:56:38 -07:00
Rhet Turnbull
a4b4f1c288 Feature help no selection 1036 (#1042)
* Added validation for --selected

* Added test for #999 (project_info) that I missed on last branch
2023-04-08 14:34:08 -07:00
Rhet Turnbull
f668ecf0c4 Updated CHANGELOG.md [skip ci] 2023-04-08 12:10:43 -07:00
Rhet Turnbull
058b56092f Release 0.59.2 (#1041) 2023-04-08 12:04:54 -07:00
Rhet Turnbull
d2b7783125 Added shallow json() option, #1038 (#1040) 2023-04-08 11:10:29 -07:00
Rhet Turnbull
f4a743468d Fix for json() failing on photos in projects, #999 (#1039) 2023-04-08 09:49:31 -07:00
Rhet Turnbull
adac985309 Fix non-incrementing photo count, #999 2023-04-08 09:09:33 -07:00
allcontributors[bot]
4939324d2f add dvdkon as a contributor for code (#1037)
* update README.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-04-07 08:17:00 -07:00
Christian Clauss
1a3b4c2afe Lint Python twice: 1. Whole repo, 2. Exclude tests/ (#1035)
* Lint Python twice: 1. Whole repo, 2. Exclude tests/

Both runs take less than a second so...
`line-length=366` is more reasonable`
```
    - run: pip install --user ruff  # Lint Python twice: 1. Whole repo, 2. Exclude tests/
    - run: ruff --format=github --line-length=7228 --target-version=py39
                --ignore=E402,E712,E721,E722,E741,F401,F403,F405,F541,F601,F811,F822,F841 .
    - run: ruff --format=github --line-length=366 --target-version=py39
                --exclude=tests/ --ignore=E402,E712,E722,E741,F401,F403,F405,F541,F822,F841 .
```

* Update configoptions.py
2023-04-07 08:14:51 -07:00
Rhet Turnbull
1b16a39cef Updated CHANGELOG.md [skip ci] 2023-04-02 22:07:10 -07:00
Rhet Turnbull
0d31a152be Test fix for Ventura 2023-04-02 22:00:35 -07:00
Rhet Turnbull
34fbd87fc6 Release 0.59.1 (#1034) 2023-04-02 21:18:18 -07:00
Christian Clauss
3ddfea6a3d Fix two undefined names in phototemplate.py (#1030)
* Fix two undefined names in phototemplate.py

% `ruff --exit-zero --select=E9,F63,F7,F82,YTT .`
```
osxphotos/cli/report_writer.py:22:1: F822 Undefined name `ExportReportWriterSqlite` in `__all__`
osxphotos/cli/report_writer.py:22:1: F822 Undefined name `SyncReportWriterSqlite` in `__all__`
osxphotos/phototemplate.py:1156:108: F821 Undefined name `vals`
osxphotos/phototemplate.py:1685:29: F821 Undefined name `PhotoInfo`
Found 4 errors.
```

* Avoid cyclic imports
2023-04-02 19:45:33 -07:00
Christian Clauss
8b59615ff7 GitHub Action to lint Python code (#1025)
* GitHub Action to lint Python code

[Ruff](https://beta.ruff.rs/) supports [over 500 lint rules](https://beta.ruff.rs/docs/rules) including bandit, isort, pylint, pyupgrade, and flake8 plus its plugins, and is written in Rust for speed.

The `ruff` Action uses minimal steps to __run in ~5 seconds__, rapidly providing intuitive GitHub Annotations to contributors.

![image](https://user-images.githubusercontent.com/3709715/223758136-afc386d2-70aa-4eff-953a-2c2d82ceea23.png)

* ruff --target-version=py39
2023-04-02 19:45:13 -07:00
allcontributors[bot]
c590a16d70 add cclauss as a contributor for code (#1033)
* update README.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-04-02 19:44:49 -07:00
Rhet Turnbull
36ce86c4a6 Removed file locking, performance improvements (#1032) 2023-04-02 19:42:28 -07:00
185 changed files with 4277 additions and 1686 deletions

View File

@@ -262,7 +262,8 @@
"bug",
"ideas",
"test",
"code"
"code",
"doc"
]
},
{
@@ -529,10 +530,67 @@
"avatar_url": "https://avatars.githubusercontent.com/u/2597142?v=4",
"profile": "https://github.com/pekingduck",
"contributions": [
"ideas"
"ideas",
"bug"
]
},
{
"login": "cclauss",
"name": "Christian Clauss",
"avatar_url": "https://avatars.githubusercontent.com/u/3709715?v=4",
"profile": "https://www.patreon.com/cclauss",
"contributions": [
"code"
]
},
{
"login": "dvdkon",
"name": "dvdkon",
"avatar_url": "https://avatars.githubusercontent.com/u/3526303?v=4",
"profile": "https://github.com/dvdkon",
"contributions": [
"code"
]
},
{
"login": "wernerzj",
"name": "wernerzj",
"avatar_url": "https://avatars.githubusercontent.com/u/130370930?v=4",
"profile": "https://github.com/wernerzj",
"contributions": [
"bug"
]
},
{
"login": "rajscode",
"name": "rajscode",
"avatar_url": "https://avatars.githubusercontent.com/u/99123253?v=4",
"profile": "https://github.com/rajscode",
"contributions": [
"bug",
"doc"
]
},
{
"login": "MaxLyt",
"name": "MaxLyt",
"avatar_url": "https://avatars.githubusercontent.com/u/136200430?v=4",
"profile": "https://github.com/MaxLyt",
"contributions": [
"bug"
]
},
{
"login": "ces3001",
"name": "ces3001",
"avatar_url": "https://avatars.githubusercontent.com/u/23762610?v=4",
"profile": "https://github.com/ces3001",
"contributions": [
"bug"
]
}
],
"contributorsPerLine": 7,
"skipCi": true
"skipCi": true,
"commitType": "docs"
}

View File

@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.59.0
current_version = 0.60.5
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
serialize = {major}.{minor}.{patch}

View File

@@ -3,15 +3,23 @@ name: Tests
on: [push, pull_request]
jobs:
ruff: # https://beta.ruff.rs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: pip install --user ruff # Lint Python twice: 1. Whole repo, 2. Exclude tests/
- run: ruff --format=github --line-length=7228 --target-version=py39
--ignore=E402,E712,E721,E722,E741,F401,F403,F405,F541,F601,F811,F822,F841 .
- run: ruff --format=github --line-length=366 --target-version=py39
--exclude=tests/ --ignore=E402,E722,E741,F401,F403,F405,F541,F822,F841 .
build:
runs-on: ${{ matrix.os }}
if: "!contains(github.event.head_commit.message, '[skip ci]')"
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
strategy:
max-parallel: 4
matrix:
os: [macos-latest]
os: [macos-latest, ubuntu-latest]
python-version: ['3.9', '3.10', '3.11']
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}

View File

@@ -23,6 +23,7 @@ In addition to a command line interface, OSXPhotos provides a access to a Python
* [CommentInfo](#commentinfo)
* [LikeInfo](#likeinfo)
* [AdjustmentsInfo](#adjustmentsinfo)
* [PhotoTables](#phototables)
* [Raw Photos](#raw-photos)
* [Template System](#template-system)
* [ExifTool](#exiftoolExifTool)
@@ -338,10 +339,7 @@ to your function. You can then do whatever you want with the photos.
from __future__ import annotations
import osxphotos
from osxphotos.cli import (
selection_command,
verbose,
)
from osxphotos.cli import selection_command, verbose
@selection_command
@@ -389,7 +387,6 @@ Python < 3.11.
For example, the following code will work on Python >= 3.11. This code is available in the `examples` directory as
[concurrent_export.py](https://github.com/RhetTbull/osxphotos/blob/main/examples/concurrent_export.py).
```python
"""Example for concurrent export of photos using osxphotos.PhotoExporter.export()
@@ -1182,6 +1179,15 @@ Returns True if photo is a [cloud asset](#iscloudasset) and is synched to iCloud
**Note**: Applies to master (original) photo only. It's possible for the master to be in iCloud but a local edited version is not yet synched to iCloud. `incloud` provides status of only the master photo. osxphotos does not yet provide a means to determine if the edited version is in iCloud. If you need this feature, please open an [issue](https://github.com/RhetTbull/osxphotos/issues).
#### `syndicated`
Return true if photo was shared via syndication (e.g. via Messages, etc.); these are photos that appear in "Shared with you" album. Photos 8+ only; returns None if not Photos 8+.
#### `saved_to_library`
Return True if syndicated photo has been saved to library; returns False if photo is not syndicated or has not been saved to the library.
Syndicated photos are photos that appear in "Shared with you" album. Photos 8+ only; returns None if not Photos 8+.
#### `uti`
Returns Uniform Type Identifier (UTI) for the current version of the image, for example: 'public.jpeg' or 'com.apple. quicktime-movie'. If the image has been edited, `uti` will return the UTI for the edited image, otherwise it will return the UTI for the original image.
@@ -1395,6 +1401,12 @@ Returns a unique fingerprint for the original photo file. This is a hash of the
Returns a unique digest of the photo's properties and metadata; useful for detecting changes in any property/metadata of the photo.
#### `tables()`
Returns a PhotoTables object which provides access to the underlying SQLite database tables for the photo.
See [PhotoTables](#phototables) for more details. This is useful for debugging or developing new features but
is not intended for general use.
#### `json()`
Returns a JSON representation of all photo info.
@@ -2157,6 +2169,37 @@ Returns a JSON representation of the FaceInfo instance.
* `adj_version_info`: version info for the application which made the adjustments to the photo decoded from the adjustments data.
* `asdict()`: dict representation of the AdjustmentsInfo object; contains all properties with exception of `plist`.
### PhotoTables
[PhotoInfo.tables](#tables) returns a PhotoTables object that contains information about the tables in the Photos database that contain information about the photo.
The following properties are available:
* `ZASSET`
* `ZADDITIONALASSETATTRIBUTES`
* `ZDETECTEDFACE`
* `ZPERSON`
Each of these properties returns a `Table` object that provides access to the row(s) in the table that correspond to the photo.
The Table object has dynamically created properties that correspond to the associated column in the table and return a tuple of values for that column.
```pycon
>>> photo.tables().ZADDITIONALASSETATTRIBUTES.ZTITLE
("St. James's Park",)
```
The Table object also provides a `rows()` method which returns a list a of tuples for the matching rows in the table
and a `rows_dict()` method which returns a list of dicts for the matching rows in the table.
```pycon
>>> photo.tables().ZASSET.rows()
[(6, 3, 35, 0, 0, 0, 0, 0, 0, None, None, None, None, None, 0, 0, 1, 0, 0, 0, 0, -100, 0, 1, 0, 1356, 0, 0, 0, 0, 0, 0, 0, 1, 6192599813128215, 1, 2814835671629878, 1, 0, 3, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 2047, 7, None, 8, None, None, None, None, None, None, None, None, 3, 6, 6, 6, None, 6, 4, None, None, 8, 4, None, 2, None, 3, None, 3, None, None, 585926209.859624, 596906868.198932, 689981763.374756, None, None, None, 0.5, 561129492.501, 0.0, 596906868.198932, None, 0.03816793893129771, None, 51.50357167, -0.1318055, 689982854.802854, 0.6494140625, 0.0, 561129492.501, None, None, None, None, None, None, None, 'D', 'DC99FBDD-7A52-4100-A5BB-344131646C30.jpeg', None, 'sRGB IEC61966-2.1', 'public.jpeg', 'DC99FBDD-7A52-4100-A5BB-344131646C30', b'Ki\t@\x01\x00\x00\x00\td\tH\x01\x00\x00\x00\x93\\\tL\x01\x00\x00\x00\x1aK\x0c\x03\x0c\xa8q\x92\x00\x12C\x0c\x03\x0c"\r\x90\x00\x00<\x0c\x03\x08"\x19\x80\x00', b'\xca\xebV\tu\xc0I@/j\xf7\xab\x00\xdf\xc0\xbf\xcd\xcc\xcc\xcc\xcc\xcc\x04@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')]
>>> photo.tables().ZASSET.rows_dict()
[{'Z_PK': 6, 'Z_ENT': 3, 'Z_OPT': 35, 'ZACTIVELIBRARYSCOPEPARTICIPATIONSTATE': 0, 'ZAVALANCHEPICKTYPE': 0, 'ZBUNDLESCOPE': 0, 'ZCAMERAPROCESSINGADJUSTMENTSTATE': 0, 'ZCLOUDDELETESTATE': 0, 'ZCLOUDDOWNLOADREQUESTS': 0, 'ZCLOUDHASCOMMENTSBYME': None, 'ZCLOUDHASCOMMENTSCONVERSATION': None, 'ZCLOUDHASUNSEENCOMMENTS': None, 'ZCLOUDISDELETABLE': None, 'ZCLOUDISMYASSET': None, 'ZCLOUDLOCALSTATE': 0, 'ZCLOUDPLACEHOLDERKIND': 0, 'ZCOMPLETE': 1, 'ZDEFERREDPROCESSINGNEEDED': 0, 'ZDEPTHTYPE': 0, 'ZDERIVEDCAMERACAPTUREDEVICE': 0, 'ZDUPLICATEASSETVISIBILITYSTATE': 0, 'ZFACEAREAPOINTS': -100, 'ZFAVORITE': 0, 'ZHASADJUSTMENTS': 1, 'ZHDRTYPE': 0, 'ZHEIGHT': 1356, 'ZHIDDEN': 0, 'ZHIGHFRAMERATESTATE': 0, 'ZISMAGICCARPET': 0, 'ZKIND': 0, 'ZKINDSUBTYPE': 0, 'ZLIBRARYSCOPESHARESTATE': 0, 'ZMONOSKITYPE': 0, 'ZORIENTATION': 1, 'ZPACKEDACCEPTABLECROPRECT': 6192599813128215, 'ZPACKEDBADGEATTRIBUTES': 1, 'ZPACKEDPREFERREDCROPRECT': 2814835671629878, 'ZPLAYBACKSTYLE': 1, 'ZPLAYBACKVARIATION': 0, 'ZSAVEDASSETTYPE': 3, 'ZSEARCHINDEXREBUILDSTATE': 0, 'ZSYNDICATIONSTATE': 0, 'ZTHUMBNAILINDEX': 5, 'ZTRASHEDSTATE': 0, 'ZVIDEOCPDURATIONVALUE': 0, 'ZVIDEOCPVISIBILITYSTATE': 0, 'ZVIDEODEFERREDPROCESSINGNEEDED': 0, 'ZVIDEOKEYFRAMETIMESCALE': 0, 'ZVIDEOKEYFRAMEVALUE': 0, 'ZVISIBILITYSTATE': 0, 'ZWIDTH': 2047, 'ZADDITIONALATTRIBUTES': 7, 'ZCLOUDFEEDASSETSENTRY': None, 'ZCOMPUTEDATTRIBUTES': 8, 'ZCONVERSATION': None, 'ZDAYGROUPHIGHLIGHTBEINGASSETS': None, 'ZDAYGROUPHIGHLIGHTBEINGEXTENDEDASSETS': None, 'ZDAYGROUPHIGHLIGHTBEINGKEYASSETPRIVATE': None, 'ZDAYGROUPHIGHLIGHTBEINGKEYASSETSHARED': None, 'ZDAYGROUPHIGHLIGHTBEINGSUMMARYASSETS': None, 'ZDUPLICATEMETADATAMATCHINGALBUM': None, 'ZDUPLICATEPERCEPTUALMATCHINGALBUM': None, 'ZEXTENDEDATTRIBUTES': 3, 'ZHIGHLIGHTBEINGASSETS': 6, 'ZHIGHLIGHTBEINGEXTENDEDASSETS': 6, 'ZHIGHLIGHTBEINGKEYASSETPRIVATE': 6, 'ZHIGHLIGHTBEINGKEYASSETSHARED': None, 'ZHIGHLIGHTBEINGSUMMARYASSETS': 6, 'ZIMPORTSESSION': 4, 'ZLIBRARYSCOPE': None, 'ZMASTER': None, 'ZMEDIAANALYSISATTRIBUTES': 8, 'ZMOMENT': 4, 'ZMOMENTSHARE': None, 'ZMONTHHIGHLIGHTBEINGKEYASSETPRIVATE': 2, 'ZMONTHHIGHLIGHTBEINGKEYASSETSHARED': None, 'ZPHOTOANALYSISATTRIBUTES': 3, 'ZTRASHEDBYPARTICIPANT': None, 'ZYEARHIGHLIGHTBEINGKEYASSETPRIVATE': 3, 'ZYEARHIGHLIGHTBEINGKEYASSETSHARED': None, 'Z_FOK_CLOUDFEEDASSETSENTRY': None, 'ZADDEDDATE': 585926209.859624, 'ZADJUSTMENTTIMESTAMP': 596906868.198932, 'ZANALYSISSTATEMODIFICATIONDATE': 689981763.374756, 'ZCLOUDBATCHPUBLISHDATE': None, 'ZCLOUDLASTVIEWEDCOMMENTDATE': None, 'ZCLOUDSERVERPUBLISHDATE': None, 'ZCURATIONSCORE': 0.5, 'ZDATECREATED': 561129492.501, 'ZDURATION': 0.0, 'ZFACEADJUSTMENTVERSION': 596906868.198932, 'ZHDRGAIN': None, 'ZHIGHLIGHTVISIBILITYSCORE': 0.03816793893129771, 'ZLASTSHAREDDATE': None, 'ZLATITUDE': 51.50357167, 'ZLONGITUDE': -0.1318055, 'ZMODIFICATIONDATE': 689982854.802854, 'ZOVERALLAESTHETICSCORE': 0.6494140625, 'ZPROMOTIONSCORE': 0.0, 'ZSORTTOKEN': 561129492.501, 'ZTRASHEDDATE': None, 'ZAVALANCHEUUID': None, 'ZCLOUDASSETGUID': None, 'ZCLOUDBATCHID': None, 'ZCLOUDCOLLECTIONGUID': None, 'ZCLOUDOWNERHASHEDPERSONID': None, 'ZDELETEREASON': None, 'ZDIRECTORY': 'D', 'ZFILENAME': 'DC99FBDD-7A52-4100-A5BB-344131646C30.jpeg', 'ZMEDIAGROUPUUID': None, 'ZORIGINALCOLORSPACE': 'sRGB IEC61966-2.1', 'ZUNIFORMTYPEIDENTIFIER': 'public.jpeg', 'ZUUID': 'DC99FBDD-7A52-4100-A5BB-344131646C30', 'ZIMAGEREQUESTHINTS': b'Ki\t@\x01\x00\x00\x00\td\tH\x01\x00\x00\x00\x93\\\tL\x01\x00\x00\x00\x1aK\x0c\x03\x0c\xa8q\x92\x00\x12C\x0c\x03\x0c"\r\x90\x00\x00<\x0c\x03\x08"\x19\x80\x00', 'ZLOCATIONDATA': b'\xca\xebV\tu\xc0I@/j\xf7\xab\x00\xdf\xc0\xbf\xcd\xcc\xcc\xcc\xcc\xcc\x04@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'}]
```
### Raw Photos
Handling raw photos in `osxphotos` requires a bit of extra work. Raw photos in Photos can be imported in two different ways: 1) a single raw photo with no associated JPEG image is imported 2) a raw+JPEG pair is imported -- two separate images with same file stem (e.g. `IMG_0001.CR2` and `IMG_001.JPG`) are imported.
@@ -2456,7 +2499,7 @@ cog.out(get_template_field_table())
|{cr}|A carriage return: '\r'|
|{crlf}|A carriage return + line feed: '\r\n'|
|{tab}|:A tab: '\t'|
|{osxphotos_version}|The osxphotos version, e.g. '0.59.0'|
|{osxphotos_version}|The osxphotos version, e.g. '0.60.5'|
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|{album}|Album(s) photo is contained in|
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|

View File

@@ -2,6 +2,168 @@
All notable changes to this project will be documented in this file.
## [v0.60.4](https://github.com/RhetTbull/osxphotos/compare/v0.60.3...v0.60.4)
Updated testing / compatibility matrix to include macOS 13.4.
## [v0.60.4] - 2023-06-18
### Fixed
### Added
### Removed
### Changed
### Contributors
- [@RhetTbull](https://github.com/RhetTbull/osxphotos) - code & testing
## [v0.60.3](https://github.com/RhetTbull/osxphotos/compare/v0.60.2...v0.60.3)
Ventura introduced a "shared with you" album which shows photos shared via Messages (and possible other apps). These show up in the Photos library in the
"Shared with you" album but the images are stored in a different location that regular images so osxphotos could not previously access the images.
It can now do so.
### [v0.60.3] 2023-06-18
#### Fixed
#### Added
- `PhotoInfo.syndicated` property to identify syndicated photos.
- `PhotoInfo.saved_to_library` property to identify syndicated photos that have been saved to the library.
- `--syndicated`/`--not-syndicated`, `--saved-to-library`/`--not-saved-to-library` query options.
- `find()` function in `osxphotos repl` to search for files in the active Photos library directory.
#### Removed
#### Changed
#### Contributors
- [@RhetTbull](https://github.com/RhetTbull) for code.
## [v0.60.2](https://github.com/RhetTbull/osxphotos/compare/v0.60.1...v0.60.2)
Performance Improvements for --download-missing
### [v0.60.2] - 2023-06-17
#### Fixed
- Performance improvements for `osxphotos export` when used with `--download-missing` or `--sidecar XMP` options. (#1086)
#### Added
#### Changed
#### Contributors
- [@RhetTbull](https://github.com/RhetTbull) for code changes.
- [@MaxLyt](https://github.com/MaxLyt) for finding the issue.
## [v0.60.1](https://github.com/RhetTbull/osxphotos/compare/v0.60.0...v0.60.1)
Hot fix for a bug with in-memory database and --dry-run.
### 14 May 2023
#### Fixed
- Fixed crash with --dry-run with large export database (#1071)
#### Contributors
- @RhetTbull for code changes.
- @rajscode for identifying the bug and filing a detailed bug report.
## [v0.60.0](https://github.com/RhetTbull/osxphotos/compare/v0.59.3...v0.60.0)
Linux Support: adds support for using a subset of osxphotos capabilities on Linux.
### 07 May 2023
#### Added
- osxphotos now supports Linux (tested on Ubuntu 22.04); some commands are macOS only and will not be available (nor shown) on Linux. Huge thank you to [@dvdkon](https://github.com/dvdkon) for doing the Linux port!
- Added `PhotoTables` API and `tables` property to `PhotoInfo` to access underlying SQL tables for a photo.
#### Changed
- Added macOS 13.3 to supported versions table.
#### Contributors
- @dvdkon for Linux port.
- @RhetTbull for code changes.
- @pekingduck for bug report.
- @cclause for updating ruff test runner.
## [v0.59.3](https://github.com/RhetTbull/osxphotos/compare/v0.59.2...v0.59.3)
Bug fixes for memory leak, crash during export
### 10 April 2023
#### Fixed
- Fixed memory leak in export (#1047)
- Fixed crash during export (#1046)
- Fixed large crash log size (#1048)
#### Changed
- Added better help for no selection with --selected (#1036)
- Changed PhotoInfo.asdict() and PhotoInfo.json() to allow deep or shallow option (#1038)
- Updated development docs (#1043)
#### Contributors
- @RhetTbull for code changes
- @wernerzj for finding bug with memory leak
- @rajscode for finding export crash
- @oPromessa for development docs fix
## [v0.59.2](https://github.com/RhetTbull/osxphotos/compare/v0.59.1...v0.59.2)
Bug Fix for Export
### 08 April 2023
#### Fixed
- Fixed error on export when photo belonged to a project (#999)
- Fixed large increase in export database size (#999)
#### Changed
- Added indent, shallow args to PhotoInfo.json() (#1038)
#### Contributors
- @RhetTbull for code
- @oPromessa for finding bugs, running tests
## [v0.59.1](https://github.com/RhetTbull/osxphotos/compare/v0.59.0...v0.59.1)
Performance Boost
### 2 April 2023
#### Changed
- Removed lock files from export code (speed boost for NAS export, see #999); will need to eventually add this back for multithreaded export
- Optimized some code in export CLI to speed export
- Some linting fixed for move to ruff
-
#### Contributors
- [@RhetTbull](https://github.com/RhetTbull) for code changes.
- [@cclauss](https://github.com/cclauss) for linting fixes
## [v0.59.0](https://github.com/RhetTbull/osxphotos/compare/v0.58.2...v0.59.0)
### 1 April 2023
@@ -96,15 +258,15 @@ See example code in [concurrent_export.py](https://github.com/RhetTbull/osxphoto
#### Added
- `--migrate-photos-library` option added to `osxphotos exportdb` to migrate the export database from one Photos library to another. This is useful when moving to a new computer but maintaining the existing osxphotos export. Thanks to @swduncan for the idea. (#990)
- `--migrate-photos-library` option added to `osxphotos exportdb` to migrate the export database from one Photos library to another. This is useful when moving to a new computer but maintaining the existing osxphotos export. Thanks to @swduncan for the idea. (#990)
#### Fixed
- Fixed a bug in `osxphotos export --cleanup` to handle files which could not be deleted. Thanks to @oPromessa for finding this and suggesting the fix. (#987)
- Fixed a bug in `osxphotos export --cleanup` to handle files which could not be deleted. Thanks to @oPromessa for finding this and suggesting the fix. (#987)
#### Internal
- Fixed a bug that caused `rich_echo()` to not display rich text if `--verbose` wasn't specified.
- Fixed a bug that caused `rich_echo()` to not display rich text if `--verbose` wasn't specified.
#### Contributors To This Release
@@ -112,10 +274,10 @@ See example code in [concurrent_export.py](https://github.com/RhetTbull/osxphoto
- [@Promessa](https://github.com/promessa) who found the cleanup bug and suggested a code fix.
- [@swduncan](https://github.com/swduncan) who suggested the library migrate use case.
## [v0.57.1](https://github.com/RhetTbull/osxphotos/compare/v0.57.0...v0.57.1)
### 12 February 2023
### Added show command, bug fix, refactoring
A bug fix and some refactoring to prepare for adding a parallel export mode. Also added `osxphotos show` command.
@@ -138,7 +300,6 @@ A bug fix and some refactoring to prepare for adding a parallel export mode. Als
- [@RhetTbull](https://github.com/RhetTbull)
- [@aa599](https://github.com/aa599) for reporting the timezone bug and suggesting change to `--uuid-from-file`
## [v0.57.0](https://github.com/RhetTbull/osxphotos/compare/v0.56.7...v0.57.0)
### 5 February 2023
@@ -170,7 +331,7 @@ for building simple command line tools.
#### Contributors
- @RhetTbull
- @RhetTbull
## [v0.56.7](https://github.com/RhetTbull/osxphotos/compare/v0.56.6...v0.56.7)
@@ -197,7 +358,6 @@ for building simple command line tools.
- @pweaver - thanks for finding the bug with `--dry-run` and `--finder-tag-keywords`
- @eecue for providing testing data
## [v0.56.6](https://github.com/RhetTbull/osxphotos/compare/v0.56.5...v0.56.6)
### 22 January 2023
@@ -246,7 +406,6 @@ for building simple command line tools.
- Thanks to @djbeadle for idea of adding SearchInfo.source
- Thanks for @oPromessa for testing `import` speed-ups and providing test data
### [v0.56.3](https://github.com/RhetTbull/osxphotos/compare/v0.56.2...v0.56.3)
### 16 January 2023
@@ -283,7 +442,7 @@ for building simple command line tools.
#### Contributors to this release
- @RhetTbull - Added AI scores to `osxphotos inspect`
- @RhetTbull - Added AI scores to `osxphotos inspect`
- @mave2k - Documentation fixes
- @oPromessa - Bug fix for metadata in `osxphotos import`
@@ -297,7 +456,6 @@ for building simple command line tools.
- Added new `osxphotos sync` command to sync metadata between libraries (#887)
## [v0.56.0](https://github.com/RhetTbull/osxphotos/compare/v0.55.7...v0.56.0)
### 13 January 2023
@@ -310,6 +468,7 @@ for building simple command line tools.
- Added PhotoInfo.fingerprint (#900)
#### Changed
- Added --profile, --watch, --breakpoint, --debug as global options; previously these worked only with export
#### New Contributors
@@ -317,6 +476,7 @@ for building simple command line tools.
- Added @oPromessa as a contributor for code
- Added @johnsturgeon as a contributor for bug, and doc
- Added @qkeddy as a contributor for ideas, and data
## [v0.55.7](https://github.com/RhetTbull/osxphotos/compare/v0.55.6...v0.55.7)
### 1 January 2023
@@ -335,6 +495,7 @@ for building simple command line tools.
### Updates for timewarp and export when reading/writing QuickTime dates
#### Changed
- Added QuickTime:ContentCreateDate as a source for `osxphotos timewarp`
- Write QuickTime:ContentCreateDate when exporting with --exiftool for `osxphotos export`
@@ -2124,7 +2285,7 @@ Thanks to @PetrochukM for identifying this and providing code!
- doc: start with examples before the export reference [`7c7bf1b`](https://github.com/RhetTbull/osxphotos/commit/7c7bf1be6b6382a995a4e17906adfd8720d0a1c3)
- Updated dependencies in README.md [`b1cab32`](https://github.com/RhetTbull/osxphotos/commit/b1cab32ff4c7b65ae4c9a5a9a11c175dbd487c0a)
- remove extra spaces [`a59bb5b`](https://github.com/RhetTbull/osxphotos/commit/a59bb5b02f10fa554dae346a7271be37f50d8bcc)
- Adding back dependency https://github.com/RhetTbull/PhotoScript) [`7c8bfc8`](https://github.com/RhetTbull/osxphotos/commit/7c8bfc811ab3a93dabadf1655f7d0e217d6c7b01)
- Adding back dependency <https://github.com/RhetTbull/PhotoScript>) [`7c8bfc8`](https://github.com/RhetTbull/osxphotos/commit/7c8bfc811ab3a93dabadf1655f7d0e217d6c7b01)
## [v0.39.6](https://github.com/RhetTbull/osxphotos/compare/v0.39.5...v0.39.6)

View File

@@ -7,10 +7,10 @@
[![Downloads](https://static.pepy.tech/personalized-badge/osxphotos?period=month&units=international_system&left_color=black&right_color=brightgreen&left_text=downloads/month)](https://pepy.tech/project/osxphotos)
[![subreddit](https://img.shields.io/reddit/subreddit-subscribers/osxphotos?style=social)](https://www.reddit.com/r/osxphotos/)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-55-orange.svg?style=flat)](#contributors)
[![All Contributors](https://img.shields.io/badge/all_contributors-61-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.
OSXPhotos provides the ability to interact with and query Apple's Photos.app library on macOS and Linux. 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.
<p align="center"><img src="docs/screencast/demo.gif?raw=true" width="713" height="430"/></p>
@@ -33,20 +33,24 @@ OSXPhotos provides the ability to interact with and query Apple's Photos.app lib
## Supported operating systems
Only works on macOS (aka Mac OS X). Tested on macOS Sierra (10.12.6) through macOS Monterey (12.0.1). Tested on both x86 and Apple silicon (M1).
Tested on Ubuntu Linux and macOS. Many features are only available on macOS.
On Linux, macOS-specific features of the CLI will not be available (these will not be shown in the help output).
The export and query CLI commands as well as the Python API will work on Linux which enables you to export photos
from a Photos library on a Linux machine.
Tested on macOS Sierra (10.12.6) through macOS Ventura (13.3). Tested on both x86 and Apple silicon (M1).
| macOS Version | macOS name | Photos.app version |
| ----------------- |------------|:-------------------|
| 13.0 | Ventura | 8.0 ✅ * |
| 12.0 - 12.6 | Monterey | 7.0 ✅ * |
| 13.0 - 13.4 | Ventura | 8.0 ✅ |
| 12.0 - 12.6 | Monterey | 7.0 ✅ |
| 10.16, 11.0-11.4 | Big Sur | 6.0 ✅ |
| 10.15.1 - 10.15.7 | Catalina | 5.0 ✅ |
| 10.14.5, 10.14.6 | Mojave | 4.0 ✅ |
| 10.13.6 | High Sierra| 3.0 ✅ |
| 10.12.6 | Sierra | 2.0 ✅ |
\* Some features may not be fully supported on Monterey and Ventura. Notably, `--use-photokit` and `--download-missing` may or may not work depending on your configuration; this is a known issue that will be fixed if I can find a solution. Many users successfully use OSXPhotos on Monterey without problem.
This package will read Photos databases for any supported version on any supported macOS version. E.g. you can read a database created with Photos 5.0 on MacOS 10.15 on a machine running macOS 10.12 and vice versa.
Requires python >= `3.9`.
@@ -70,6 +74,14 @@ Once you've installed osxphotos with pipx, to upgrade to the latest version:
pipx upgrade osxphotos
**Note**: When installing other packages with homebrew, homebrew may update the version of Python installed which would then cause any app (including osxphotos) installed with `pipx` to fail. If this happens, the easiest fix is to reinstall osxphotos with:
pipx reinstall osxphotos
Alternatively, you can reinstall all apps installed with `pipx` with:
pipx reinstall-all
### Installation using pip
You can also install directly from [pypi](https://pypi.org/project/osxphotos/):
@@ -865,6 +877,16 @@ Options:
been synched)
--not-incloud Search for photos that are not in iCloud (have
not been synched)
--syndicated Search for photos that have been shared via
syndication ('Shared with You' album via
Messages, etc.)
--not-syndicated Search for photos that have not been shared
via syndication ('Shared with You' album via
Messages, etc.)
--saved-to-library Search for syndicated photos that have saved
to the library
--not-saved-to-library Search for syndicated photos that have not
saved to the library
--regex REGEX TEMPLATE Search for photos where TEMPLATE matches
regular expression REGEX. For example, to find
photos in an album that begins with 'Beach': '
@@ -1440,22 +1462,23 @@ option to re-export the entire library thus rebuilding the
Extended Attributes
Some options (currently '--finder-tag-template', '--finder-tag-keywords',
'-xattr-template') write additional metadata accessible by Spotlight to
facilitate searching. For example, --finder-tag-keyword writes all keywords
(including any specified by '--keyword-template' or other options) to Finder
tags that are searchable in Spotlight using the syntax: 'tag:tagname'. For
example, if you have images with keyword "Travel" then using '--finder-tag-
keywords' you could quickly find those images in the Finder by typing
'tag:Travel' in the Spotlight search bar. Finder tags are written to the
'com.apple.metadata:_kMDItemUserTags' extended attribute. Unlike EXIF
metadata, extended attributes do not modify the actual file; the metadata is
written to extended attributes associated with the file and the Spotlight
metadata database. Most cloud storage services do not synch extended
attributes. Dropbox does sync them and any changes to a file's extended
attributes will cause Dropbox to re-sync the files.
Some options (currently '--finder-tag-template', '--finder-tag-keywords',
'-xattr-template') write additional metadata accessible by Spotlight
to facilitate searching. For example, --finder-tag-keyword writes all
keywords (including any specified by '--keyword-template' or other
options) to Finder tags that are searchable in Spotlight using the syntax:
'tag:tagname'. For example, if you have images with keyword "Travel"
then using '--finder-tag-keywords' you could quickly find those images
in the Finder by typing 'tag:Travel' in the Spotlight search bar.
Finder tags are written to the 'com.apple.metadata:_kMDItemUserTags'
extended attribute. Unlike EXIF metadata, extended attributes do not
modify the actual file; the metadata is written to extended attributes
associated with the file and the Spotlight metadata database. Most
cloud storage services do not synch extended attributes. Dropbox does
sync them and any changes to a file's extended attributes will cause
Dropbox to re-sync the files.
The following attributes may be used with '--xattr-template':
The following attributes may be used with '--xattr-template':
Attribute Description
@@ -2094,7 +2117,7 @@ Substitution Description
{cr} A carriage return: '\r'
{crlf} A carriage return + line feed: '\r\n'
{tab} :A tab: '\t'
{osxphotos_version} The osxphotos version, e.g. '0.59.0'
{osxphotos_version} The osxphotos version, e.g. '0.60.5'
{osxphotos_cmd_line} The full command line used to run osxphotos
The following substitutions may result in multiple values. Thus if specified
@@ -2581,7 +2604,7 @@ The following template field substitutions are availabe for use the templating s
|{cr}|A carriage return: '\r'|
|{crlf}|A carriage return + line feed: '\r\n'|
|{tab}|:A tab: '\t'|
|{osxphotos_version}|The osxphotos version, e.g. '0.59.0'|
|{osxphotos_version}|The osxphotos version, e.g. '0.60.5'|
|{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|
@@ -2621,7 +2644,7 @@ I'll gladly consider pull requests for bug fixes or feature implementations.
If you have an interesting example that shows usage of this package, submit an issue or pull request and i'll include it or link to it.
Testing against "real world" Photos libraries would be especially helpful. If you discover issues in testing against your Photos libraries, please open an issue. I've done extensive testing against my own Photos library but that's a since data point and I'm certain there are issues lurking in various edge cases I haven't discovered yet.
Testing against "real world" Photos libraries would be especially helpful. If you discover issues in testing against your Photos libraries, please open an issue. I've done extensive testing against my own Photos library but that's a single data point and I'm certain there are issues lurking in various edge cases I haven't discovered yet.
### Contributors ✨
@@ -2665,7 +2688,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mkirkland4874"><img src="https://avatars.githubusercontent.com/u/36466711?v=4?s=75" width="75px;" alt="mkirkland4874"/><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" valign="top" width="14.28%"><a href="https://github.com/jcommisso07"><img src="https://avatars.githubusercontent.com/u/3111054?v=4?s=75" width="75px;" alt="Joseph Commisso"/><br /><sub><b>Joseph Commisso</b></sub></a><br /><a href="#data-jcommisso07" title="Data">🔣</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dssinger"><img src="https://avatars.githubusercontent.com/u/1817903?v=4?s=75" width="75px;" alt="David Singer"/><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" valign="top" width="14.28%"><a href="https://github.com/oPromessa"><img src="https://avatars.githubusercontent.com/u/21261491?v=4?s=75" width="75px;" alt="oPromessa"/><br /><sub><b>oPromessa</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3AoPromessa" title="Bug reports">🐛</a> <a href="#ideas-oPromessa" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/RhetTbull/osxphotos/commits?author=oPromessa" title="Tests">⚠️</a> <a href="https://github.com/RhetTbull/osxphotos/commits?author=oPromessa" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/oPromessa"><img src="https://avatars.githubusercontent.com/u/21261491?v=4?s=75" width="75px;" alt="oPromessa"/><br /><sub><b>oPromessa</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3AoPromessa" title="Bug reports">🐛</a> <a href="#ideas-oPromessa" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/RhetTbull/osxphotos/commits?author=oPromessa" title="Tests">⚠️</a> <a href="https://github.com/RhetTbull/osxphotos/commits?author=oPromessa" title="Code">💻</a> <a href="https://github.com/RhetTbull/osxphotos/commits?author=oPromessa" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://spencerchang.me"><img src="https://avatars.githubusercontent.com/u/14796580?v=4?s=75" width="75px;" alt="Spencer Chang"/><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>
<tr>
@@ -2701,7 +2724,15 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aa599"><img src="https://avatars.githubusercontent.com/u/37746269?v=4?s=75" width="75px;" alt="aa599"/><br /><sub><b>aa599</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Aaa599" title="Bug reports">🐛</a> <a href="#ideas-aa599" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/swduncan"><img src="https://avatars.githubusercontent.com/u/2053195?v=4?s=75" width="75px;" alt="Steve Duncan"/><br /><sub><b>Steve Duncan</b></sub></a><br /><a href="#ideas-swduncan" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.projany.com"><img src="https://avatars.githubusercontent.com/u/15144745?v=4?s=75" width="75px;" alt="Ian Moir"/><br /><sub><b>Ian Moir</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Aianmmoir" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pekingduck"><img src="https://avatars.githubusercontent.com/u/2597142?v=4?s=75" width="75px;" alt="Peking Duck"/><br /><sub><b>Peking Duck</b></sub></a><br /><a href="#ideas-pekingduck" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pekingduck"><img src="https://avatars.githubusercontent.com/u/2597142?v=4?s=75" width="75px;" alt="Peking Duck"/><br /><sub><b>Peking Duck</b></sub></a><br /><a href="#ideas-pekingduck" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Apekingduck" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.patreon.com/cclauss"><img src="https://avatars.githubusercontent.com/u/3709715?v=4?s=75" width="75px;" alt="Christian Clauss"/><br /><sub><b>Christian Clauss</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/commits?author=cclauss" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dvdkon"><img src="https://avatars.githubusercontent.com/u/3526303?v=4?s=75" width="75px;" alt="dvdkon"/><br /><sub><b>dvdkon</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/commits?author=dvdkon" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wernerzj"><img src="https://avatars.githubusercontent.com/u/130370930?v=4?s=75" width="75px;" alt="wernerzj"/><br /><sub><b>wernerzj</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Awernerzj" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rajscode"><img src="https://avatars.githubusercontent.com/u/99123253?v=4?s=75" width="75px;" alt="rajscode"/><br /><sub><b>rajscode</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Arajscode" title="Bug reports">🐛</a> <a href="https://github.com/RhetTbull/osxphotos/commits?author=rajscode" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MaxLyt"><img src="https://avatars.githubusercontent.com/u/136200430?v=4?s=75" width="75px;" alt="MaxLyt"/><br /><sub><b>MaxLyt</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3AMaxLyt" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ces3001"><img src="https://avatars.githubusercontent.com/u/23762610?v=4?s=75" width="75px;" alt="ces3001"/><br /><sub><b>ces3001</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Aces3001" title="Bug reports">🐛</a></td>
</tr>
</tbody>
</table>

View File

@@ -7,7 +7,7 @@ These are notes for developers working on osxphotos. They're mostly to help me r
- Clone the repo: `git clone git@github.com:RhetTbull/osxphotos.git`
- Create a virtual environment and activate it: `python3 -m venv venv` then `source venv/bin/activate`. I use [pyenv](https://github.com/pyenv/pyenv) with [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv) to manage my virtual environments
- Install the requirements: `pip install -r requirements.txt`
- Install the development requirements: `pip install -r requirements-dev.txt`
- Install the development requirements: `pip install -r dev_requirements.txt`
- Install osxphotos: `pip install -e .`
## Running tests

View File

@@ -2,7 +2,7 @@ build
bump2version==1.0.1
cogapp>=3.3.0,<4.0.0
furo
m2r2==0.3.3
m2r2==0.3.3.post2
pdbpp
pyinstaller==5.6.2
pytest-cov==4.0.0

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: 97939ee226e568d1b630529e47e51a47
config: 7fe2be825ec9ab49bbb41359aee23684
tags: 645f666f9bcd5a90fca523b33c5a78b7

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../genindex.html" /><link rel="search" title="Search" href="../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>Overview: module code - osxphotos 0.59.0 documentation</title>
<title>Overview: module code - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../index.html"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="../index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../index.html">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos._constants - osxphotos 0.59.0 documentation</title>
<title>osxphotos._constants - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -215,9 +215,6 @@
<span class="c1"># Apple Epoch is Jan 1, 2001</span>
<span class="n">TIME_DELTA</span> <span class="o">=</span> <span class="p">(</span><span class="n">datetime</span><span class="p">(</span><span class="mi">2001</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="mi">0</span><span class="p">,</span> <span class="mi">0</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="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span><span class="o">.</span><span class="n">total_seconds</span><span class="p">()</span>
<span class="c1"># Unicode format to use for comparing strings</span>
<span class="n">UNICODE_FORMAT</span> <span class="o">=</span> <span class="s2">&quot;NFC&quot;</span>
<span class="c1"># which Photos library database versions have been tested</span>
<span class="c1"># Photos 2.0 (10.12.6) == 2622</span>
<span class="c1"># Photos 3.0 (10.13.6) == 3301</span>
@@ -325,6 +322,8 @@
<span class="p">(</span><span class="s2">&quot;13&quot;</span><span class="p">,</span> <span class="s2">&quot;0&quot;</span><span class="p">),</span>
<span class="p">(</span><span class="s2">&quot;13&quot;</span><span class="p">,</span> <span class="s2">&quot;1&quot;</span><span class="p">),</span>
<span class="p">(</span><span class="s2">&quot;13&quot;</span><span class="p">,</span> <span class="s2">&quot;2&quot;</span><span class="p">),</span>
<span class="p">(</span><span class="s2">&quot;13&quot;</span><span class="p">,</span> <span class="s2">&quot;3&quot;</span><span class="p">),</span>
<span class="p">(</span><span class="s2">&quot;13&quot;</span><span class="p">,</span> <span class="s2">&quot;4&quot;</span><span class="p">),</span>
<span class="p">]</span>
<span class="c1"># Photos 5 has persons who are empty string if unidentified face</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos.albuminfo - osxphotos 0.59.0 documentation</title>
<title>osxphotos.albuminfo - osxphotos 0.59.2 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.59.2 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.59.2 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -247,9 +247,8 @@
<span class="sd"> ValueError: raised if len(values) != len(sort_keys)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">values</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">len</span><span class="p">(</span><span class="n">sort_keys</span><span class="p">):</span>
<span class="k">return</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&quot;values and sort_keys must have same length&quot;</span><span class="p">)</span>
<span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="o">*</span><span class="nb">sorted</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="n">sort_keys</span><span class="p">,</span> <span class="n">values</span><span class="p">))))[</span><span class="mi">1</span><span class="p">]</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&quot;values and sort_keys must be same length&quot;</span><span class="p">)</span>
<span class="k">return</span> <span class="p">[</span><span class="n">x</span> <span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="n">sort_keys</span><span class="p">,</span> <span class="n">values</span><span class="p">))]</span>
<span class="k">class</span> <span class="nc">AlbumInfoBaseClass</span><span class="p">:</span>
@@ -363,14 +362,13 @@
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_owner</span>
<span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return album info as a dict&quot;&quot;&quot;</span>
<span class="sd">&quot;&quot;&quot;Return album info as a dict; does not include photos&quot;&quot;&quot;</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s2">&quot;uuid&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
<span class="s2">&quot;creation_date&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">creation_date</span><span class="p">,</span>
<span class="s2">&quot;start_date&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">start_date</span><span class="p">,</span>
<span class="s2">&quot;end_date&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">end_date</span><span class="p">,</span>
<span class="s2">&quot;owner&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">owner</span><span class="p">,</span>
<span class="s2">&quot;photos&quot;</span><span class="p">:</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">uuid</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">photos</span><span class="p">],</span>
<span class="p">}</span>
<span class="k">def</span> <span class="fm">__len__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
@@ -496,7 +494,7 @@
<span class="p">)</span></div>
<div class="viewcode-block" id="AlbumInfo.asdict"><a class="viewcode-back" href="../../reference.html#osxphotos.AlbumInfo.asdict">[docs]</a> <span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return album info as a dict&quot;&quot;&quot;</span>
<span class="sd">&quot;&quot;&quot;Return album info as a dict; does not include photos&quot;&quot;&quot;</span>
<span class="n">dict_data</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;title&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">title</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;folder_names&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">folder_names</span>
@@ -559,14 +557,13 @@
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_photos</span>
<div class="viewcode-block" id="ImportInfo.asdict"><a class="viewcode-back" href="../../reference.html#osxphotos.ImportInfo.asdict">[docs]</a> <span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return import info as a dict&quot;&quot;&quot;</span>
<span class="sd">&quot;&quot;&quot;Return import info as a dict; does not include photos&quot;&quot;&quot;</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s2">&quot;uuid&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
<span class="s2">&quot;creation_date&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">creation_date</span><span class="p">,</span>
<span class="s2">&quot;start_date&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">start_date</span><span class="p">,</span>
<span class="s2">&quot;end_date&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">end_date</span><span class="p">,</span>
<span class="s2">&quot;title&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">title</span><span class="p">,</span>
<span class="s2">&quot;photos&quot;</span><span class="p">:</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">uuid</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">photos</span><span class="p">],</span>
<span class="p">}</span></div>
<span class="k">def</span> <span class="fm">__bool__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
@@ -583,7 +580,25 @@
<span class="sd"> Projects are cards, calendars, slideshows, etc.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="o">...</span></div>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">folder_names</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return hierarchical list of folders the album is contained in</span>
<span class="sd"> the folder list is in form:</span>
<span class="sd"> [&quot;Top level folder&quot;, &quot;sub folder 1&quot;, &quot;sub folder 2&quot;, ...]</span>
<span class="sd"> or empty list if album is not in any folders</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="c1"># projects are not in folders</span>
<span class="k">return</span> <span class="p">[]</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">folder_list</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Returns list of FolderInfo objects for each folder the album is contained in</span>
<span class="sd"> or empty list if album is not in any folders</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="c1"># projects are not in folders</span>
<span class="k">return</span> <span class="p">[]</span></div>
<div class="viewcode-block" id="FolderInfo"><a class="viewcode-back" href="../../reference.html#osxphotos.FolderInfo">[docs]</a><span class="k">class</span> <span class="nc">FolderInfo</span><span class="p">:</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos.exiftool - osxphotos 0.58.1 documentation</title>
<title>osxphotos.exiftool - osxphotos 0.59.1 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.58.1 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.59.1 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.58.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.59.1 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos.export_db - osxphotos 0.59.0 documentation</title>
<title>osxphotos.export_db - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -224,7 +224,7 @@
<span class="kn">from</span> <span class="nn">._constants</span> <span class="kn">import</span> <span class="n">OSXPHOTOS_EXPORT_DB</span><span class="p">,</span> <span class="n">SQLITE_CHECK_SAME_THREAD</span>
<span class="kn">from</span> <span class="nn">._version</span> <span class="kn">import</span> <span class="n">__version__</span>
<span class="kn">from</span> <span class="nn">.fileutil</span> <span class="kn">import</span> <span class="n">FileUtil</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">normalize_fs_path</span>
<span class="kn">from</span> <span class="nn">.unicode</span> <span class="kn">import</span> <span class="n">normalize_fs_path</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span>
<span class="s2">&quot;ExportDB&quot;</span><span class="p">,</span>
@@ -1118,32 +1118,35 @@
<span class="sd"> returns: connection to the database</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">dbfile</span><span class="p">):</span>
<span class="n">conn</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="k">if</span> <span class="ow">not</span> <span class="n">conn</span><span class="p">:</span>
<span class="c1"># database doesn&#39;t exist so create it in-memory</span>
<span class="n">src</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="k">if</span> <span class="ow">not</span> <span class="n">src</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">Exception</span><span class="p">(</span><span class="s2">&quot;Error getting connection to in-memory database&quot;</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_create_or_migrate_db_tables</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_create_or_migrate_db_tables</span><span class="p">(</span><span class="n">src</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">was_created</span> <span class="o">=</span> <span class="kc">True</span>
<span class="bp">self</span><span class="o">.</span><span class="n">was_upgraded</span> <span class="o">=</span> <span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">sqlite3</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">dbfile</span><span class="p">,</span> <span class="n">check_same_thread</span><span class="o">=</span><span class="n">SQLITE_CHECK_SAME_THREAD</span><span class="p">)</span>
<span class="n">dbdump</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dump_db</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
<span class="n">conn</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">version</span> <span class="o">=</span> <span class="n">OSXPHOTOS_EXPORTDB_VERSION</span>
<span class="k">return</span> <span class="n">src</span>
<span class="c1"># Create a database in memory and import from the dump</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">sqlite3</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span>
<span class="s2">&quot;:memory:&quot;</span><span class="p">,</span> <span class="n">check_same_thread</span><span class="o">=</span><span class="n">SQLITE_CHECK_SAME_THREAD</span>
<span class="p">)</span>
<span class="n">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span><span class="o">.</span><span class="n">executescript</span><span class="p">(</span><span class="n">dbdump</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
<span class="bp">self</span><span class="o">.</span><span class="n">was_created</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">version_info</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_database_version</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
<span class="k">if</span> <span class="n">version_info</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">OSXPHOTOS_EXPORTDB_VERSION</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_create_or_migrate_db_tables</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">was_upgraded</span> <span class="o">=</span> <span class="p">(</span><span class="n">version_info</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">OSXPHOTOS_EXPORTDB_VERSION</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">was_upgraded</span> <span class="o">=</span> <span class="p">()</span>
<span class="c1"># database exists so copy it to memory</span>
<span class="n">src</span> <span class="o">=</span> <span class="n">sqlite3</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">dbfile</span><span class="p">,</span> <span class="n">check_same_thread</span><span class="o">=</span><span class="n">SQLITE_CHECK_SAME_THREAD</span><span class="p">)</span>
<span class="c1"># Create a database in memory by backing up the on-disk database</span>
<span class="n">dst</span> <span class="o">=</span> <span class="n">sqlite3</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s2">&quot;:memory:&quot;</span><span class="p">,</span> <span class="n">check_same_thread</span><span class="o">=</span><span class="n">SQLITE_CHECK_SAME_THREAD</span><span class="p">)</span>
<span class="k">with</span> <span class="n">dst</span><span class="p">:</span>
<span class="n">src</span><span class="o">.</span><span class="n">backup</span><span class="p">(</span><span class="n">dst</span><span class="p">,</span> <span class="n">pages</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
<span class="n">src</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">was_created</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">version_info</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_database_version</span><span class="p">(</span><span class="n">dst</span><span class="p">)</span>
<span class="k">if</span> <span class="n">version_info</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">OSXPHOTOS_EXPORTDB_VERSION</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_create_or_migrate_db_tables</span><span class="p">(</span><span class="n">dst</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">was_upgraded</span> <span class="o">=</span> <span class="p">(</span><span class="n">version_info</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">OSXPHOTOS_EXPORTDB_VERSION</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">was_upgraded</span> <span class="o">=</span> <span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">version</span> <span class="o">=</span> <span class="n">OSXPHOTOS_EXPORTDB_VERSION</span>
<span class="k">return</span> <span class="n">conn</span>
<span class="k">return</span> <span class="n">dst</span>
<span class="k">def</span> <span class="nf">_get_db_connection</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;return db connection to in memory database&quot;&quot;&quot;</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos.fileutil - osxphotos 0.58.1 documentation</title>
<title>osxphotos.fileutil - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.58.1 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.58.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -206,9 +206,12 @@
<span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
<span class="kn">from</span> <span class="nn">tempfile</span> <span class="kn">import</span> <span class="n">TemporaryDirectory</span>
<span class="kn">import</span> <span class="nn">Foundation</span>
<span class="kn">from</span> <span class="nn">.imageconverter</span> <span class="kn">import</span> <span class="n">ImageConverter</span>
<span class="kn">from</span> <span class="nn">.platform</span> <span class="kn">import</span> <span class="n">is_macos</span>
<span class="kn">from</span> <span class="nn">.unicode</span> <span class="kn">import</span> <span class="n">normalize_fs_path</span>
<span class="k">if</span> <span class="n">is_macos</span><span class="p">:</span>
<span class="kn">import</span> <span class="nn">Foundation</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;FileUtilABC&quot;</span><span class="p">,</span> <span class="s2">&quot;FileUtilMacOS&quot;</span><span class="p">,</span> <span class="s2">&quot;FileUtilShUtil&quot;</span><span class="p">,</span> <span class="s2">&quot;FileUtil&quot;</span><span class="p">,</span> <span class="s2">&quot;FileUtilNoOp&quot;</span><span class="p">]</span>
@@ -287,6 +290,9 @@
<span class="k">if</span> <span class="n">src</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">or</span> <span class="n">dest</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&quot;src and dest must not be None&quot;</span><span class="p">,</span> <span class="n">src</span><span class="p">,</span> <span class="n">dest</span><span class="p">)</span>
<span class="n">src</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">src</span><span class="p">)</span>
<span class="n">dest</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">src</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span><span class="s2">&quot;src file does not appear to exist&quot;</span><span class="p">,</span> <span class="n">src</span><span class="p">)</span>
@@ -312,6 +318,9 @@
<span class="sd"> OSError if copy fails</span>
<span class="sd"> TypeError if either path is None</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">src</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">src</span><span class="p">)</span>
<span class="n">dest</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">src</span><span class="p">,</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">):</span>
<span class="n">src</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">src</span><span class="p">)</span>
@@ -332,6 +341,7 @@
<span class="nd">@classmethod</span>
<span class="k">def</span> <span class="nf">unlink</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">filepath</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;unlink filepath; if it&#39;s pathlib.Path, use Path.unlink, otherwise use os.unlink&quot;&quot;&quot;</span>
<span class="n">filepath</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">filepath</span><span class="p">,</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">):</span>
<span class="n">filepath</span><span class="o">.</span><span class="n">unlink</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
@@ -340,6 +350,7 @@
<span class="nd">@classmethod</span>
<span class="k">def</span> <span class="nf">rmdir</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">dirpath</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;remove directory filepath; dirpath must be empty&quot;&quot;&quot;</span>
<span class="n">dirpath</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">dirpath</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">dirpath</span><span class="p">,</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">):</span>
<span class="n">dirpath</span><span class="o">.</span><span class="n">rmdir</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
@@ -348,6 +359,7 @@
<span class="nd">@classmethod</span>
<span class="k">def</span> <span class="nf">utime</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">times</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Set the access and modified time of path.&quot;&quot;&quot;</span>
<span class="n">path</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">utime</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">times</span><span class="o">=</span><span class="n">times</span><span class="p">)</span>
<span class="nd">@classmethod</span>
@@ -363,6 +375,9 @@
<span class="sd"> Does not do a byte-by-byte comparison.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">f1</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">f1</span><span class="p">)</span>
<span class="n">f2</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">f2</span><span class="p">)</span>
<span class="n">s1</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_sig</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">stat</span><span class="p">(</span><span class="n">f1</span><span class="p">))</span>
<span class="k">if</span> <span class="n">mtime1</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">s1</span> <span class="o">=</span> <span class="p">(</span><span class="n">s1</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">s1</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="nb">int</span><span class="p">(</span><span class="n">mtime1</span><span class="p">))</span>
@@ -385,6 +400,7 @@
<span class="k">if</span> <span class="ow">not</span> <span class="n">s2</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">False</span>
<span class="n">f1</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">f1</span><span class="p">)</span>
<span class="n">s1</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_sig</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">stat</span><span class="p">(</span><span class="n">f1</span><span class="p">))</span>
<span class="k">if</span> <span class="n">s1</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">!=</span> <span class="n">stat</span><span class="o">.</span><span class="n">S_IFREG</span> <span class="ow">or</span> <span class="n">s2</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">!=</span> <span class="n">stat</span><span class="o">.</span><span class="n">S_IFREG</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">False</span>
@@ -393,6 +409,7 @@
<span class="nd">@classmethod</span>
<span class="k">def</span> <span class="nf">file_sig</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">f1</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;return os.stat signature for file f1 as tuple of (mode, size, mtime)&quot;&quot;&quot;</span>
<span class="n">f1</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">f1</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_sig</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">stat</span><span class="p">(</span><span class="n">f1</span><span class="p">))</span>
<span class="nd">@classmethod</span>
@@ -407,6 +424,8 @@
<span class="sd"> Returns:</span>
<span class="sd"> True if success, otherwise False</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">src_file</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">src_file</span><span class="p">)</span>
<span class="n">dest_file</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">dest_file</span><span class="p">)</span>
<span class="n">converter</span> <span class="o">=</span> <span class="n">ImageConverter</span><span class="p">()</span>
<span class="k">return</span> <span class="n">converter</span><span class="o">.</span><span class="n">write_jpeg</span><span class="p">(</span>
<span class="n">src_file</span><span class="p">,</span> <span class="n">dest_file</span><span class="p">,</span> <span class="n">compression_quality</span><span class="o">=</span><span class="n">compression_quality</span>
@@ -424,6 +443,8 @@
<span class="sd"> Name of renamed file (dest)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">src</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">src</span><span class="p">)</span>
<span class="n">dest</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">rename</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">src</span><span class="p">),</span> <span class="nb">str</span><span class="p">(</span><span class="n">dest</span><span class="p">))</span>
<span class="k">return</span> <span class="n">dest</span>
@@ -468,6 +489,9 @@
<span class="sd"> OSError if copy fails</span>
<span class="sd"> TypeError if either path is None</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">src</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">src</span><span class="p">)</span>
<span class="n">dest</span> <span class="o">=</span> <span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">src</span><span class="p">,</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">):</span>
<span class="n">src</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">src</span><span class="p">)</span>
@@ -485,7 +509,7 @@
<span class="k">return</span> <span class="kc">True</span>
<div class="viewcode-block" id="FileUtil"><a class="viewcode-back" href="../../reference.html#osxphotos.FileUtil">[docs]</a><span class="k">class</span> <span class="nc">FileUtil</span><span class="p">(</span><span class="n">FileUtilMacOS</span><span class="p">):</span>
<div class="viewcode-block" id="FileUtil"><a class="viewcode-back" href="../../reference.html#osxphotos.FileUtil">[docs]</a><span class="k">class</span> <span class="nc">FileUtil</span><span class="p">(</span><span class="n">FileUtilShUtil</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Various file utilities&quot;&quot;&quot;</span>
<span class="k">pass</span></div>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos.photoexporter - osxphotos 0.59.0 documentation</title>
<title>osxphotos.photoexporter - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -198,6 +198,8 @@
<span></span><span class="sd">&quot;&quot;&quot; PhotoExport class to export photos</span>
<span class="sd">&quot;&quot;&quot;</span>
<span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
<span class="kn">import</span> <span class="nn">dataclasses</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">logging</span>
@@ -205,13 +207,11 @@
<span class="kn">import</span> <span class="nn">pathlib</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="kn">import</span> <span class="nn">typing</span> <span class="k">as</span> <span class="nn">t</span>
<span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">namedtuple</span> <span class="c1"># pylint: disable=syntax-error</span>
<span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">asdict</span><span class="p">,</span> <span class="n">dataclass</span>
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
<span class="kn">from</span> <span class="nn">enum</span> <span class="kn">import</span> <span class="n">Enum</span>
<span class="kn">from</span> <span class="nn">types</span> <span class="kn">import</span> <span class="n">SimpleNamespace</span>
<span class="kn">import</span> <span class="nn">photoscript</span>
<span class="kn">from</span> <span class="nn">mako.template</span> <span class="kn">import</span> <span class="n">Template</span>
<span class="kn">from</span> <span class="nn">._constants</span> <span class="kn">import</span> <span class="p">(</span>
@@ -232,15 +232,10 @@
<span class="kn">from</span> <span class="nn">.exiftool</span> <span class="kn">import</span> <span class="n">ExifTool</span><span class="p">,</span> <span class="n">ExifToolCaching</span><span class="p">,</span> <span class="n">exiftool_can_write</span><span class="p">,</span> <span class="n">get_exiftool_path</span>
<span class="kn">from</span> <span class="nn">.export_db</span> <span class="kn">import</span> <span class="n">ExportDB</span><span class="p">,</span> <span class="n">ExportDBTemp</span>
<span class="kn">from</span> <span class="nn">.fileutil</span> <span class="kn">import</span> <span class="n">FileUtil</span>
<span class="kn">from</span> <span class="nn">.photokit</span> <span class="kn">import</span> <span class="p">(</span>
<span class="n">PHOTOS_VERSION_CURRENT</span><span class="p">,</span>
<span class="n">PHOTOS_VERSION_ORIGINAL</span><span class="p">,</span>
<span class="n">PHOTOS_VERSION_UNADJUSTED</span><span class="p">,</span>
<span class="n">PhotoKitFetchFailed</span><span class="p">,</span>
<span class="n">PhotoLibrary</span><span class="p">,</span>
<span class="p">)</span>
<span class="kn">from</span> <span class="nn">.phototemplate</span> <span class="kn">import</span> <span class="n">RenderOptions</span>
<span class="kn">from</span> <span class="nn">.platform</span> <span class="kn">import</span> <span class="n">is_macos</span>
<span class="kn">from</span> <span class="nn">.rich_utils</span> <span class="kn">import</span> <span class="n">add_rich_markup_tag</span>
<span class="kn">from</span> <span class="nn">.unicode</span> <span class="kn">import</span> <span class="n">normalize_fs_path</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="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="p">(</span>
<span class="n">hexdigest</span><span class="p">,</span>
@@ -252,6 +247,18 @@
<span class="n">unlock_filename</span><span class="p">,</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">is_macos</span><span class="p">:</span>
<span class="kn">import</span> <span class="nn">photoscript</span>
<span class="kn">from</span> <span class="nn">.photokit</span> <span class="kn">import</span> <span class="p">(</span>
<span class="n">PHOTOS_VERSION_CURRENT</span><span class="p">,</span>
<span class="n">PHOTOS_VERSION_ORIGINAL</span><span class="p">,</span>
<span class="n">PHOTOS_VERSION_UNADJUSTED</span><span class="p">,</span>
<span class="n">PhotoKitFetchFailed</span><span class="p">,</span>
<span class="n">PhotoLibrary</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span>
<span class="s2">&quot;ExportError&quot;</span><span class="p">,</span>
<span class="s2">&quot;ExportOptions&quot;</span><span class="p">,</span>
@@ -266,6 +273,10 @@
<span class="c1"># retry if download_missing/use_photos_export fails the first time (which sometimes it does)</span>
<span class="n">MAX_PHOTOSCRIPT_RETRIES</span> <span class="o">=</span> <span class="mi">3</span>
<span class="c1"># Global to hold the compiled XMP template</span>
<span class="c1"># This is expensive to compile so we only want to do it once</span>
<span class="n">_global_xmp_template</span><span class="p">:</span> <span class="n">Template</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># return values for _should_update_photo</span>
<span class="k">class</span> <span class="nc">ShouldUpdate</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
@@ -434,6 +445,9 @@
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">asdict</span><span class="p">())</span>
<span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">&quot;StagedFiles(</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span><span class="si">}</span><span class="s2">)&quot;</span>
<span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s2">&quot;original&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">original</span><span class="p">,</span>
@@ -669,6 +683,11 @@
<span class="n">dest</span><span class="p">,</span> <span class="n">options</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_should_convert_to_jpeg</span><span class="p">(</span><span class="n">dest</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span>
<span class="c1"># stage files for export by finding path in local library or downloading from iCloud as appropriate</span>
<span class="c1"># for `--download-missing` and `--update` case, this may cause unnecessary downloads</span>
<span class="c1"># as it will download the file even if it&#39;s not needed (won&#39;t be checked until the _should_update_photo() call from _export_photo()</span>
<span class="c1"># fixing this will require major refactoring of the export code, see #1086</span>
<span class="c1"># leaving it for now as this should not be a common use case</span>
<span class="c1"># (if using `--update` it is much better to be using &quot;Download originals to this Mac&quot; in Photos)</span>
<span class="n">staged_files</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_stage_photos_for_export</span><span class="p">(</span><span class="n">options</span><span class="p">)</span>
<span class="n">src</span> <span class="o">=</span> <span class="n">staged_files</span><span class="o">.</span><span class="n">edited</span> <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">edited</span> <span class="k">else</span> <span class="n">staged_files</span><span class="o">.</span><span class="n">original</span>
@@ -855,8 +874,16 @@
<span class="k">return</span> <span class="n">lock_filename</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">if</span> <span class="n">lock</span> <span class="k">else</span> <span class="n">filename</span>
<span class="c1"># if overwrite==False and #increment==False, export should fail if file exists</span>
<span class="k">if</span> <span class="n">dest</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="ow">and</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
<span class="p">[</span><span class="n">options</span><span class="o">.</span><span class="n">increment</span><span class="p">,</span> <span class="n">options</span><span class="o">.</span><span class="n">update</span><span class="p">,</span> <span class="n">options</span><span class="o">.</span><span class="n">force_update</span><span class="p">,</span> <span class="n">options</span><span class="o">.</span><span class="n">overwrite</span><span class="p">]</span>
<span class="k">if</span> <span class="p">(</span>
<span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
<span class="p">[</span>
<span class="n">options</span><span class="o">.</span><span class="n">increment</span><span class="p">,</span>
<span class="n">options</span><span class="o">.</span><span class="n">update</span><span class="p">,</span>
<span class="n">options</span><span class="o">.</span><span class="n">force_update</span><span class="p">,</span>
<span class="n">options</span><span class="o">.</span><span class="n">overwrite</span><span class="p">,</span>
<span class="p">]</span>
<span class="p">)</span>
<span class="ow">and</span> <span class="n">dest</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span>
<span class="p">):</span>
<span class="k">raise</span> <span class="ne">FileExistsError</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">&quot;destination exists (</span><span class="si">{</span><span class="n">dest</span><span class="si">}</span><span class="s2">); overwrite=</span><span class="si">{</span><span class="n">options</span><span class="o">.</span><span class="n">overwrite</span><span class="si">}</span><span class="s2">, increment=</span><span class="si">{</span><span class="n">options</span><span class="o">.</span><span class="n">increment</span><span class="si">}</span><span class="s2">&quot;</span>
@@ -915,9 +942,18 @@
<span class="k">return</span> <span class="n">dest</span>
<span class="k">def</span> <span class="nf">_should_update_photo</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span> <span class="n">src</span><span class="p">:</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">,</span> <span class="n">dest</span><span class="p">:</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="n">ExportOptions</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">t</span><span class="o">.</span><span class="n">Literal</span><span class="p">[</span><span class="kc">True</span><span class="p">,</span> <span class="kc">False</span><span class="p">]:</span>
<span class="sd">&quot;&quot;&quot;Return True if photo should be updated, else False&quot;&quot;&quot;</span>
<span class="bp">self</span><span class="p">,</span> <span class="n">src</span><span class="p">:</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span> <span class="n">dest</span><span class="p">:</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="n">ExportOptions</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span> <span class="o">|</span> <span class="n">ShouldUpdate</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Return True if photo should be updated, else False</span>
<span class="sd"> Args:</span>
<span class="sd"> src (pathlib.Path | None): source path; if None, photo is missing and</span>
<span class="sd"> any checks that require src will return True</span>
<span class="sd"> dest (pathlib.Path): destination path</span>
<span class="sd"> Returns:</span>
<span class="sd"> False if photo should not be updated otherwise a truthy ShouldUpdate value</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="c1"># NOTE: The order of certain checks is important</span>
<span class="c1"># read the comments below to understand why before changing</span>
@@ -931,11 +967,11 @@
<span class="c1"># photo doesn&#39;t exist in database, should update</span>
<span class="k">return</span> <span class="n">ShouldUpdate</span><span class="o">.</span><span class="n">NOT_IN_DATABASE</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">export_as_hardlink</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">dest</span><span class="o">.</span><span class="n">samefile</span><span class="p">(</span><span class="n">src</span><span class="p">):</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">export_as_hardlink</span> <span class="ow">and</span> <span class="p">(</span><span class="ow">not</span> <span class="n">src</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">dest</span><span class="o">.</span><span class="n">samefile</span><span class="p">(</span><span class="n">src</span><span class="p">)):</span>
<span class="c1"># different files, should update</span>
<span class="k">return</span> <span class="n">ShouldUpdate</span><span class="o">.</span><span class="n">HARDLINK_DIFFERENT_FILES</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">options</span><span class="o">.</span><span class="n">export_as_hardlink</span> <span class="ow">and</span> <span class="n">dest</span><span class="o">.</span><span class="n">samefile</span><span class="p">(</span><span class="n">src</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">options</span><span class="o">.</span><span class="n">export_as_hardlink</span> <span class="ow">and</span> <span class="p">(</span><span class="ow">not</span> <span class="n">src</span> <span class="ow">or</span> <span class="n">dest</span><span class="o">.</span><span class="n">samefile</span><span class="p">(</span><span class="n">src</span><span class="p">)):</span>
<span class="c1"># same file but not exporting as hardlink, should update</span>
<span class="k">return</span> <span class="n">ShouldUpdate</span><span class="o">.</span><span class="n">NOT_HARDLINK_SAME_FILES</span>
@@ -967,7 +1003,9 @@
<span class="c1"># as exiftool will be used to update edited file</span>
<span class="k">return</span> <span class="n">ShouldUpdate</span><span class="o">.</span><span class="n">EXIFTOOL_DIFFERENT</span> <span class="k">if</span> <span class="n">rv</span> <span class="k">else</span> <span class="kc">False</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">edited</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">fileutil</span><span class="o">.</span><span class="n">cmp_file_sig</span><span class="p">(</span><span class="n">src</span><span class="p">,</span> <span class="n">file_record</span><span class="o">.</span><span class="n">src_sig</span><span class="p">):</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">edited</span> <span class="ow">and</span> <span class="p">(</span>
<span class="ow">not</span> <span class="n">src</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">fileutil</span><span class="o">.</span><span class="n">cmp_file_sig</span><span class="p">(</span><span class="n">src</span><span class="p">,</span> <span class="n">file_record</span><span class="o">.</span><span class="n">src_sig</span><span class="p">)</span>
<span class="p">):</span>
<span class="c1"># edited file in Photos doesn&#39;t match what was last exported</span>
<span class="k">return</span> <span class="n">ShouldUpdate</span><span class="o">.</span><span class="n">EDITED_SIG_DIFFERENT</span>
@@ -1018,22 +1056,46 @@
<span class="c1"># download any missing files</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">download_missing</span><span class="p">:</span>
<span class="n">live_photo</span> <span class="o">=</span> <span class="n">staged</span><span class="o">.</span><span class="n">edited_live</span> <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">edited</span> <span class="k">else</span> <span class="n">staged</span><span class="o">.</span><span class="n">original_live</span>
<span class="n">missing_options</span> <span class="o">=</span> <span class="n">ExportOptions</span><span class="p">(</span>
<span class="n">edited</span><span class="o">=</span><span class="n">options</span><span class="o">.</span><span class="n">edited</span><span class="p">,</span>
<span class="n">preview</span><span class="o">=</span><span class="n">options</span><span class="o">.</span><span class="n">preview</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">staged</span><span class="o">.</span><span class="n">preview</span><span class="p">,</span>
<span class="n">raw_photo</span><span class="o">=</span><span class="n">options</span><span class="o">.</span><span class="n">raw_photo</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">staged</span><span class="o">.</span><span class="n">raw</span><span class="p">,</span>
<span class="n">live_photo</span><span class="o">=</span><span class="n">options</span><span class="o">.</span><span class="n">live_photo</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">live_photo</span><span class="p">,</span>
<span class="n">staged</span> <span class="o">|=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_stage_missing_photos_for_export</span><span class="p">(</span>
<span class="n">staged</span><span class="o">=</span><span class="n">staged</span><span class="p">,</span> <span class="n">options</span><span class="o">=</span><span class="n">options</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">use_photokit</span><span class="p">:</span>
<span class="n">missing_staged</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_stage_photo_for_export_with_photokit</span><span class="p">(</span>
<span class="n">options</span><span class="o">=</span><span class="n">missing_options</span>
<span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">missing_staged</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_stage_photo_for_export_with_applescript</span><span class="p">(</span>
<span class="n">options</span><span class="o">=</span><span class="n">missing_options</span>
<span class="p">)</span>
<span class="n">staged</span> <span class="o">|=</span> <span class="n">missing_staged</span>
<span class="k">return</span> <span class="n">staged</span>
<span class="k">def</span> <span class="nf">_stage_missing_photos_for_export</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span> <span class="n">staged</span><span class="p">:</span> <span class="n">StagedFiles</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="n">ExportOptions</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">StagedFiles</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Download and stage any missing files for export&quot;&quot;&quot;</span>
<span class="c1"># if live photo and requesting edited version need the edited live photo</span>
<span class="n">live_photo</span> <span class="o">=</span> <span class="n">staged</span><span class="o">.</span><span class="n">edited_live</span> <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">edited</span> <span class="k">else</span> <span class="n">staged</span><span class="o">.</span><span class="n">original_live</span>
<span class="c1"># is there actually a missing file? (#1086)</span>
<span class="n">something_to_download</span> <span class="o">=</span> <span class="p">(</span>
<span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">hasadjustments</span> <span class="ow">and</span> <span class="n">options</span><span class="o">.</span><span class="n">edited</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">staged</span><span class="o">.</span><span class="n">edited</span><span class="p">)</span>
<span class="ow">or</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">live_photo</span> <span class="ow">and</span> <span class="n">options</span><span class="o">.</span><span class="n">live_photo</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">live_photo</span><span class="p">)</span>
<span class="ow">or</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">has_raw</span> <span class="ow">and</span> <span class="n">options</span><span class="o">.</span><span class="n">raw_photo</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">staged</span><span class="o">.</span><span class="n">raw</span><span class="p">)</span>
<span class="ow">or</span> <span class="p">(</span><span class="n">options</span><span class="o">.</span><span class="n">preview</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">staged</span><span class="o">.</span><span class="n">preview</span><span class="p">)</span>
<span class="ow">or</span> <span class="p">(</span><span class="ow">not</span> <span class="n">options</span><span class="o">.</span><span class="n">edited</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">staged</span><span class="o">.</span><span class="n">original</span><span class="p">)</span>
<span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">something_to_download</span><span class="p">:</span>
<span class="k">return</span> <span class="n">staged</span>
<span class="n">missing_options</span> <span class="o">=</span> <span class="n">ExportOptions</span><span class="p">(</span>
<span class="n">edited</span><span class="o">=</span><span class="n">options</span><span class="o">.</span><span class="n">edited</span><span class="p">,</span>
<span class="n">preview</span><span class="o">=</span><span class="n">options</span><span class="o">.</span><span class="n">preview</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">staged</span><span class="o">.</span><span class="n">preview</span><span class="p">,</span>
<span class="n">raw_photo</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">has_raw</span> <span class="ow">and</span> <span class="n">options</span><span class="o">.</span><span class="n">raw_photo</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">staged</span><span class="o">.</span><span class="n">raw</span><span class="p">,</span>
<span class="n">live_photo</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">live_photo</span> <span class="ow">and</span> <span class="n">options</span><span class="o">.</span><span class="n">live_photo</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">live_photo</span><span class="p">,</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">use_photokit</span><span class="p">:</span>
<span class="n">missing_staged</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_stage_photo_for_export_with_photokit</span><span class="p">(</span>
<span class="n">options</span><span class="o">=</span><span class="n">missing_options</span>
<span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">missing_staged</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_stage_photo_for_export_with_applescript</span><span class="p">(</span>
<span class="n">options</span><span class="o">=</span><span class="n">missing_options</span>
<span class="p">)</span>
<span class="n">staged</span> <span class="o">|=</span> <span class="n">missing_staged</span>
<span class="k">return</span> <span class="n">staged</span>
<span class="k">def</span> <span class="nf">_stage_photo_for_export_with_photokit</span><span class="p">(</span>
@@ -1378,7 +1440,7 @@
<span class="k">try</span><span class="p">:</span>
<span class="n">fileutil</span><span class="o">.</span><span class="n">copy</span><span class="p">(</span><span class="n">src</span><span class="p">,</span> <span class="n">dest_str</span><span class="p">)</span>
<span class="n">verbose</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">&quot;Exported </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_filename</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">original_filename</span><span class="p">)</span><span class="si">}</span><span class="s2"> to </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_filepath</span><span class="p">(</span><span class="n">dest_str</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="sa">f</span><span class="s2">&quot;Exported </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_filename</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">original_filename</span><span class="p">)</span><span class="si">}</span><span class="s2"> to </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_filepath</span><span class="p">(</span><span class="n">normalize_fs_path</span><span class="p">(</span><span class="n">dest_str</span><span class="p">))</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">ExportError</span><span class="p">(</span>
@@ -1409,7 +1471,7 @@
<span class="c1"># set data in the database</span>
<span class="k">with</span> <span class="n">export_db</span><span class="o">.</span><span class="n">create_or_get_file_record</span><span class="p">(</span><span class="n">dest_str</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span><span class="p">)</span> <span class="k">as</span> <span class="n">rec</span><span class="p">:</span>
<span class="n">rec</span><span class="o">.</span><span class="n">photoinfo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
<span class="n">rec</span><span class="o">.</span><span class="n">photoinfo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">json</span><span class="p">(</span><span class="n">shallow</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">rec</span><span class="o">.</span><span class="n">export_options</span> <span class="o">=</span> <span class="n">options</span><span class="o">.</span><span class="n">bit_flags</span>
<span class="c1"># don&#39;t set src_sig as that is set above before any modifications by convert_to_jpeg or exiftool</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">options</span><span class="o">.</span><span class="n">ignore_signature</span><span class="p">:</span>
@@ -1718,10 +1780,10 @@
<span class="k">if</span> <span class="ow">not</span> <span class="n">options</span><span class="o">.</span><span class="n">dry_run</span><span class="p">:</span>
<span class="n">warning_</span><span class="p">,</span> <span class="n">error_</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_write_exif_data</span><span class="p">(</span><span class="n">src</span><span class="p">,</span> <span class="n">options</span><span class="o">=</span><span class="n">options</span><span class="p">)</span>
<span class="k">if</span> <span class="n">warning_</span><span class="p">:</span>
<span class="n">exiftool_results</span><span class="o">.</span><span class="n">exiftool_warning</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">dest</span><span class="p">,</span> <span class="n">warning_</span><span class="p">))</span>
<span class="n">exiftool_results</span><span class="o">.</span><span class="n">exiftool_warning</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="nb">str</span><span class="p">(</span><span class="n">dest</span><span class="p">),</span> <span class="nb">str</span><span class="p">(</span><span class="n">warning_</span><span class="p">)))</span>
<span class="k">if</span> <span class="n">error_</span><span class="p">:</span>
<span class="n">exiftool_results</span><span class="o">.</span><span class="n">exiftool_error</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">dest</span><span class="p">,</span> <span class="n">error_</span><span class="p">))</span>
<span class="n">exiftool_results</span><span class="o">.</span><span class="n">error</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">dest</span><span class="p">,</span> <span class="n">error_</span><span class="p">))</span>
<span class="n">exiftool_results</span><span class="o">.</span><span class="n">exiftool_error</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="nb">str</span><span class="p">(</span><span class="n">dest</span><span class="p">),</span> <span class="nb">str</span><span class="p">(</span><span class="n">error_</span><span class="p">)))</span>
<span class="n">exiftool_results</span><span class="o">.</span><span class="n">error</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="nb">str</span><span class="p">(</span><span class="n">dest</span><span class="p">),</span> <span class="nb">str</span><span class="p">(</span><span class="n">error_</span><span class="p">)))</span>
<span class="n">exiftool_results</span><span class="o">.</span><span class="n">exif_updated</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span>
<span class="n">exiftool_results</span><span class="o">.</span><span class="n">to_touch</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span>
@@ -2150,10 +2212,7 @@
<span class="n">options</span> <span class="o">=</span> <span class="n">options</span> <span class="ow">or</span> <span class="n">ExportOptions</span><span class="p">()</span>
<span class="n">xmp_template_file</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">_XMP_TEMPLATE_NAME</span> <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_beta</span> <span class="k">else</span> <span class="n">_XMP_TEMPLATE_NAME_BETA</span>
<span class="p">)</span>
<span class="n">xmp_template</span> <span class="o">=</span> <span class="n">Template</span><span class="p">(</span><span class="n">filename</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">_TEMPLATE_DIR</span><span class="p">,</span> <span class="n">xmp_template_file</span><span class="p">))</span>
<span class="n">xmp_template</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_xmp_template</span><span class="p">()</span>
<span class="k">if</span> <span class="n">extension</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">extension</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">photo</span><span class="o">.</span><span class="n">original_filename</span><span class="p">)</span>
@@ -2260,6 +2319,20 @@
<span class="n">xmp_str</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">line</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">xmp_str</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span> <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="o">!=</span> <span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="k">return</span> <span class="n">xmp_str</span>
<span class="k">def</span> <span class="nf">_xmp_template</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return the mako template for XMP sidecar, creating it if necessary&quot;&quot;&quot;</span>
<span class="k">global</span> <span class="n">_global_xmp_template</span>
<span class="k">if</span> <span class="n">_global_xmp_template</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">return</span> <span class="n">_global_xmp_template</span>
<span class="n">xmp_template_file</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">_XMP_TEMPLATE_NAME_BETA</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_beta</span> <span class="k">else</span> <span class="n">_XMP_TEMPLATE_NAME</span>
<span class="p">)</span>
<span class="n">_global_xmp_template</span> <span class="o">=</span> <span class="n">Template</span><span class="p">(</span>
<span class="n">filename</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">_TEMPLATE_DIR</span><span class="p">,</span> <span class="n">xmp_template_file</span><span class="p">)</span>
<span class="p">)</span>
<span class="k">return</span> <span class="n">_global_xmp_template</span>
<span class="k">def</span> <span class="nf">_write_sidecar</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="n">sidecar_str</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;write sidecar_str to filename</span>
<span class="sd"> used for exporting sidecar info&quot;&quot;&quot;</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos.photoinfo - osxphotos 0.59.0 documentation</title>
<title>osxphotos.photoinfo - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -217,7 +217,6 @@
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">Optional</span>
<span class="kn">import</span> <span class="nn">yaml</span>
<span class="kn">from</span> <span class="nn">osxmetadata</span> <span class="kn">import</span> <span class="n">OSXMetaData</span>
<span class="kn">import</span> <span class="nn">osxphotos</span>
@@ -256,15 +255,21 @@
<span class="kn">from</span> <span class="nn">.momentinfo</span> <span class="kn">import</span> <span class="n">MomentInfo</span>
<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">.photoexporter</span> <span class="kn">import</span> <span class="n">ExportOptions</span><span class="p">,</span> <span class="n">PhotoExporter</span>
<span class="kn">from</span> <span class="nn">.phototables</span> <span class="kn">import</span> <span class="n">PhotoTables</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">.platform</span> <span class="kn">import</span> <span class="n">assert_macos</span><span class="p">,</span> <span class="n">is_macos</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">.scoreinfo</span> <span class="kn">import</span> <span class="n">ScoreInfo</span>
<span class="kn">from</span> <span class="nn">.searchinfo</span> <span class="kn">import</span> <span class="n">SearchInfo</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">_get_resource_loc</span><span class="p">,</span> <span class="n">hexdigest</span><span class="p">,</span> <span class="n">list_directory</span>
<span class="k">if</span> <span class="n">is_macos</span><span class="p">:</span>
<span class="kn">from</span> <span class="nn">osxmetadata</span> <span class="kn">import</span> <span class="n">OSXMetaData</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="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;PhotoInfo&quot;</span><span class="p">,</span> <span class="s2">&quot;PhotoInfoNone&quot;</span><span class="p">,</span> <span class="s2">&quot;frozen_photoinfo_factory&quot;</span><span class="p">]</span>
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s2">&quot;osxphotos&quot;</span><span class="p">)</span>
@@ -328,8 +333,7 @@
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">hasadjustments</span> <span class="ow">and</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="n">imagedate</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;lastmodifieddate&quot;</span><span class="p">]</span>
<span class="k">if</span> <span class="n">imagedate</span><span class="p">:</span>
<span class="k">if</span> <span class="n">imagedate</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;lastmodifieddate&quot;</span><span class="p">]:</span>
<span class="n">seconds</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;imageTimeZoneOffsetSeconds&quot;</span><span class="p">]</span> <span class="ow">or</span> <span class="mi">0</span>
<span class="n">delta</span> <span class="o">=</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">seconds</span><span class="o">=</span><span class="n">seconds</span><span class="p">)</span>
<span class="n">tz</span> <span class="o">=</span> <span class="n">timezone</span><span class="p">(</span><span class="n">delta</span><span class="p">)</span>
@@ -366,6 +370,9 @@
<span class="sd">&quot;&quot;&quot;Returns candidate path for original photo on Photos &gt;= version 5&quot;&quot;&quot;</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">&quot;shared&quot;</span><span class="p">]:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_path_5_shared</span><span class="p">()</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">syndicated</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">saved_to_library</span><span class="p">:</span>
<span class="c1"># path for &quot;shared with you&quot; syndicated photos that have not yet been saved to the library</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_path_syndication</span><span class="p">()</span>
<span class="k">return</span> <span class="p">(</span>
<span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">&quot;directory&quot;</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">&quot;filename&quot;</span><span class="p">])</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">&quot;directory&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&quot;/&quot;</span><span class="p">)</span>
@@ -405,6 +412,21 @@
<span class="n">filename</span><span class="p">,</span>
<span class="p">)</span>
<span class="k">def</span> <span class="nf">_path_syndication</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return path for syndicated photo on Photos &gt;= version 8&quot;&quot;&quot;</span>
<span class="c1"># Photos 8+ stores syndicated photos in a separate directory</span>
<span class="c1"># in ~/Photos Library.photoslibrary/scopes/syndication/originals/X/UUID.ext</span>
<span class="c1"># where X is first digit of UUID</span>
<span class="n">syndication_path</span> <span class="o">=</span> <span class="s2">&quot;scopes/syndication/originals&quot;</span>
<span class="n">uuid_dir</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</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">_library_path</span><span class="p">,</span>
<span class="n">syndication_path</span><span class="p">,</span>
<span class="n">uuid_dir</span><span class="p">,</span>
<span class="bp">self</span><span class="o">.</span><span class="n">filename</span><span class="p">,</span>
<span class="p">)</span>
<span class="k">return</span> <span class="n">path</span> <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">else</span> <span class="kc">None</span>
<span class="k">def</span> <span class="nf">_path_4</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Returns candidate path for original photo on Photos &lt;= version 4&quot;&quot;&quot;</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">&quot;has_raw&quot;</span><span class="p">]:</span>
@@ -714,39 +736,26 @@
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">person_info</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;list of PersonInfo objects for person in picture&quot;&quot;&quot;</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">_personinfo</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_personinfo</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">PersonInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="p">,</span> <span class="n">pk</span><span class="o">=</span><span class="n">pk</span><span class="p">)</span> <span class="k">for</span> <span class="n">pk</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">&quot;persons&quot;</span><span class="p">]</span>
<span class="p">]</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_personinfo</span>
<span class="k">return</span> <span class="p">[</span><span class="n">PersonInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="p">,</span> <span class="n">pk</span><span class="o">=</span><span class="n">pk</span><span class="p">)</span> <span class="k">for</span> <span class="n">pk</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">&quot;persons&quot;</span><span class="p">]]</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">face_info</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;list of FaceInfo objects for faces in picture&quot;&quot;&quot;</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">_faceinfo</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">faces</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_faceinfo_uuid</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">_uuid</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_faceinfo</span> <span class="o">=</span> <span class="p">[</span><span class="n">FaceInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="p">,</span> <span class="n">pk</span><span class="o">=</span><span class="n">pk</span><span class="p">)</span> <span class="k">for</span> <span class="n">pk</span> <span class="ow">in</span> <span class="n">faces</span><span class="p">]</span>
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
<span class="c1"># no faces</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_faceinfo</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_faceinfo</span>
<span class="n">faces</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_faceinfo_uuid</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">_uuid</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_faceinfo</span> <span class="o">=</span> <span class="p">[</span><span class="n">FaceInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="p">,</span> <span class="n">pk</span><span class="o">=</span><span class="n">pk</span><span class="p">)</span> <span class="k">for</span> <span class="n">pk</span> <span class="ow">in</span> <span class="n">faces</span><span class="p">]</span>
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
<span class="c1"># no faces</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_faceinfo</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_faceinfo</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">moment_info</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Moment photo belongs to&quot;&quot;&quot;</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">_moment</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="bp">self</span><span class="o">.</span><span class="n">_moment</span> <span class="o">=</span> <span class="n">MomentInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="p">,</span> <span class="n">moment_pk</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;momentID&quot;</span><span class="p">])</span>
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_moment</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">_moment</span>
<span class="k">return</span> <span class="n">MomentInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="p">,</span> <span class="n">moment_pk</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;momentID&quot;</span><span class="p">])</span>
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">None</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">albums</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
@@ -763,65 +772,41 @@
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">burst_albums</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;If photo is burst photo, list of albums it is contained in as well as any albums the key photo is contained in, otherwise returns self.albums&quot;&quot;&quot;</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">_burst_albums</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
<span class="n">burst_albums</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">albums</span><span class="p">)</span>
<span class="k">for</span> <span class="n">photo</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_photos</span><span class="p">:</span>
<span class="k">if</span> <span class="n">photo</span><span class="o">.</span><span class="n">burst_key</span><span class="p">:</span>
<span class="n">burst_albums</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">photo</span><span class="o">.</span><span class="n">albums</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_burst_albums</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">burst_albums</span><span class="p">))</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_burst_albums</span>
<span class="n">burst_albums</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">albums</span><span class="p">)</span>
<span class="k">for</span> <span class="n">photo</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_photos</span><span class="p">:</span>
<span class="k">if</span> <span class="n">photo</span><span class="o">.</span><span class="n">burst_key</span><span class="p">:</span>
<span class="n">burst_albums</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">photo</span><span class="o">.</span><span class="n">albums</span><span class="p">)</span>
<span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">burst_albums</span><span class="p">))</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">album_info</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;list of AlbumInfo objects representing albums the photo is contained in&quot;&quot;&quot;</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">_album_info</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
<span class="n">album_uuids</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_album_uuids</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_album_info</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">AlbumInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="p">,</span> <span class="n">uuid</span><span class="o">=</span><span class="n">album</span><span class="p">)</span> <span class="k">for</span> <span class="n">album</span> <span class="ow">in</span> <span class="n">album_uuids</span>
<span class="p">]</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_album_info</span>
<span class="n">album_uuids</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_album_uuids</span><span class="p">()</span>
<span class="k">return</span> <span class="p">[</span><span class="n">AlbumInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="p">,</span> <span class="n">uuid</span><span class="o">=</span><span class="n">album</span><span class="p">)</span> <span class="k">for</span> <span class="n">album</span> <span class="ow">in</span> <span class="n">album_uuids</span><span class="p">]</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">burst_album_info</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;If photo is a burst photo, returns list of AlbumInfo objects representing albums the photo is contained in as well as albums the burst key photo is contained in, otherwise returns self.album_info.&quot;&quot;&quot;</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">_burst_album_info</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
<span class="n">burst_album_info</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">album_info</span><span class="p">)</span>
<span class="k">for</span> <span class="n">photo</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_photos</span><span class="p">:</span>
<span class="k">if</span> <span class="n">photo</span><span class="o">.</span><span class="n">burst_key</span><span class="p">:</span>
<span class="n">burst_album_info</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">photo</span><span class="o">.</span><span class="n">album_info</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_burst_album_info</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">burst_album_info</span><span class="p">))</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_burst_album_info</span>
<span class="n">burst_album_info</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">album_info</span><span class="p">)</span>
<span class="k">for</span> <span class="n">photo</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_photos</span><span class="p">:</span>
<span class="k">if</span> <span class="n">photo</span><span class="o">.</span><span class="n">burst_key</span><span class="p">:</span>
<span class="n">burst_album_info</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">photo</span><span class="o">.</span><span class="n">album_info</span><span class="p">)</span>
<span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">burst_album_info</span><span class="p">))</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">import_info</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;ImportInfo object representing import session for the photo or None if no import session&quot;&quot;&quot;</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">_import_info</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_import_info</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">ImportInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="p">,</span> <span class="n">uuid</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;import_uuid&quot;</span><span class="p">])</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">&quot;import_uuid&quot;</span><span class="p">]</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span>
<span class="k">else</span> <span class="kc">None</span>
<span class="p">)</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_import_info</span>
<span class="k">return</span> <span class="p">(</span>
<span class="n">ImportInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="p">,</span> <span class="n">uuid</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;import_uuid&quot;</span><span class="p">])</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">&quot;import_uuid&quot;</span><span class="p">]</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span>
<span class="k">else</span> <span class="kc">None</span>
<span class="p">)</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">project_info</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;list of AlbumInfo objects representing projects for the photo or None if no projects&quot;&quot;&quot;</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">_project_info</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
<span class="n">project_uuids</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_album_uuids</span><span class="p">(</span><span class="n">project</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_project_info</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">ProjectInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="p">,</span> <span class="n">uuid</span><span class="o">=</span><span class="n">album</span><span class="p">)</span> <span class="k">for</span> <span class="n">album</span> <span class="ow">in</span> <span class="n">project_uuids</span>
<span class="p">]</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_project_info</span>
<span class="n">project_uuids</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_album_uuids</span><span class="p">(</span><span class="n">project</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">return</span> <span class="p">[</span><span class="n">ProjectInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="p">,</span> <span class="n">uuid</span><span class="o">=</span><span class="n">album</span><span class="p">)</span> <span class="k">for</span> <span class="n">album</span> <span class="ow">in</span> <span class="n">project_uuids</span><span class="p">]</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">keywords</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
@@ -835,8 +820,7 @@
<span class="c1"># in this case, return None so result is the same as if title had never been set (which returns NULL)</span>
<span class="c1"># issue #512</span>
<span class="n">title</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;name&quot;</span><span class="p">]</span>
<span class="n">title</span> <span class="o">=</span> <span class="kc">None</span> <span class="k">if</span> <span class="n">title</span> <span class="o">==</span> <span class="s2">&quot;&quot;</span> <span class="k">else</span> <span class="n">title</span>
<span class="k">return</span> <span class="n">title</span>
<span class="k">return</span> <span class="kc">None</span> <span class="k">if</span> <span class="n">title</span> <span class="o">==</span> <span class="s2">&quot;&quot;</span> <span class="k">else</span> <span class="n">title</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">uuid</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
@@ -1109,33 +1093,14 @@
<span class="n">photopath</span> <span class="o">=</span> <span class="kc">None</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">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">live_photo</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">ismissing</span><span class="p">:</span>
<span class="n">live_model_id</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;live_model_id&quot;</span><span class="p">]</span>
<span class="k">if</span> <span class="n">live_model_id</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;missing live_model_id: </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">&quot;</span><span class="p">)</span>
<span class="n">photopath</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">folder_id</span><span class="p">,</span> <span class="n">file_id</span><span class="p">,</span> <span class="n">nn_id</span> <span class="o">=</span> <span class="n">_get_resource_loc</span><span class="p">(</span><span class="n">live_model_id</span><span class="p">)</span>
<span class="n">library_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">library_path</span>
<span class="n">photopath</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
<span class="n">library_path</span><span class="p">,</span>
<span class="s2">&quot;resources&quot;</span><span class="p">,</span>
<span class="s2">&quot;media&quot;</span><span class="p">,</span>
<span class="s2">&quot;master&quot;</span><span class="p">,</span>
<span class="n">folder_id</span><span class="p">,</span>
<span class="n">nn_id</span><span class="p">,</span>
<span class="sa">f</span><span class="s2">&quot;jpegvideocomplement_</span><span class="si">{</span><span class="n">file_id</span><span class="si">}</span><span class="s2">.mov&quot;</span><span class="p">,</span>
<span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">photopath</span><span class="p">):</span>
<span class="c1"># In testing, I&#39;ve seen occasional missing movie for live photo</span>
<span class="c1"># These appear to be valid -- e.g. live component hasn&#39;t been downloaded from iCloud</span>
<span class="c1"># photos 4 has &quot;isOnDisk&quot; column we could check</span>
<span class="c1"># or could do the actual check with &quot;isfile&quot;</span>
<span class="c1"># TODO: should this be a warning or debug?</span>
<span class="n">photopath</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">photopath</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">_path_live_photo_4</span><span class="p">()</span>
<span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">live_photo</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">path</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">ismissing</span><span class="p">:</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">shared</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_path_live_photo_shared_5</span><span class="p">()</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">syndicated</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">saved_to_library</span><span class="p">:</span>
<span class="c1"># syndicated (&quot;Shared with you&quot;) photos not yet saved to library</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_path_live_syndicated</span><span class="p">()</span>
<span class="n">filename</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</span><span class="p">)</span>
<span class="n">photopath</span> <span class="o">=</span> <span class="n">filename</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">joinpath</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">filename</span><span class="o">.</span><span class="n">stem</span><span class="si">}</span><span class="s2">_3.mov&quot;</span><span class="p">)</span>
<span class="n">photopath</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">photopath</span><span class="p">)</span>
@@ -1149,8 +1114,68 @@
<span class="k">return</span> <span class="n">photopath</span>
<span class="k">def</span> <span class="nf">_path_live_photo_shared_5</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return path for live photo for shared photos&quot;&quot;&quot;</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">shared</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;photo </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"> is not a shared photo&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">live_photo</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;photo </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"> is not a live photo&quot;</span><span class="p">)</span>
<span class="n">photopath</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_path_5_shared</span><span class="p">()</span>
<span class="k">if</span> <span class="n">photopath</span><span class="p">:</span>
<span class="n">photopath</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">photopath</span><span class="p">)</span><span class="o">.</span><span class="n">with_suffix</span><span class="p">(</span><span class="s2">&quot;.MOV&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">photopath</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
<span class="n">photopath</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">return</span> <span class="n">photopath</span>
<span class="k">def</span> <span class="nf">_path_live_photo_4</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return path for live edited photo for Photos &lt;= 4&quot;&quot;&quot;</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">live_photo</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">ismissing</span><span class="p">:</span>
<span class="n">live_model_id</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;live_model_id&quot;</span><span class="p">]</span>
<span class="k">if</span> <span class="n">live_model_id</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;missing live_model_id: </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">&quot;</span><span class="p">)</span>
<span class="n">photopath</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">folder_id</span><span class="p">,</span> <span class="n">file_id</span><span class="p">,</span> <span class="n">nn_id</span> <span class="o">=</span> <span class="n">_get_resource_loc</span><span class="p">(</span><span class="n">live_model_id</span><span class="p">)</span>
<span class="n">library_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">library_path</span>
<span class="n">photopath</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
<span class="n">library_path</span><span class="p">,</span>
<span class="s2">&quot;resources&quot;</span><span class="p">,</span>
<span class="s2">&quot;media&quot;</span><span class="p">,</span>
<span class="s2">&quot;master&quot;</span><span class="p">,</span>
<span class="n">folder_id</span><span class="p">,</span>
<span class="n">nn_id</span><span class="p">,</span>
<span class="sa">f</span><span class="s2">&quot;jpegvideocomplement_</span><span class="si">{</span><span class="n">file_id</span><span class="si">}</span><span class="s2">.mov&quot;</span><span class="p">,</span>
<span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">photopath</span><span class="p">):</span>
<span class="c1"># In testing, I&#39;ve seen occasional missing movie for live photo</span>
<span class="c1"># These appear to be valid -- e.g. live component hasn&#39;t been downloaded from iCloud</span>
<span class="c1"># photos 4 has &quot;isOnDisk&quot; column we could check</span>
<span class="c1"># or could do the actual check with &quot;isfile&quot;</span>
<span class="c1"># TODO: should this be a warning or debug?</span>
<span class="n">photopath</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">photopath</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">return</span> <span class="n">photopath</span>
<span class="k">def</span> <span class="nf">_path_live_syndicated</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return path for live syndicated photo on Photos &gt;= version 8&quot;&quot;&quot;</span>
<span class="c1"># Photos 8+ stores live syndicated photos in a separate directory</span>
<span class="c1"># in ~/Photos Library.photoslibrary/scopes/syndication/originals/X/UUID_3.mov</span>
<span class="c1"># where X is first digit of UUID</span>
<span class="n">syndication_path</span> <span class="o">=</span> <span class="s2">&quot;scopes/syndication/originals&quot;</span>
<span class="n">uuid_dir</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">filename</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</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">filename</span><span class="p">)</span><span class="o">.</span><span class="n">stem</span><span class="si">}</span><span class="s2">_3.mov&quot;</span>
<span class="n">live_photo</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</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">_library_path</span><span class="p">,</span>
<span class="n">syndication_path</span><span class="p">,</span>
<span class="n">uuid_dir</span><span class="p">,</span>
<span class="n">filename</span><span class="p">,</span>
<span class="p">)</span>
<span class="k">return</span> <span class="n">live_photo</span> <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">live_photo</span><span class="p">)</span> <span class="k">else</span> <span class="kc">None</span>
<span class="nd">@cached_property</span>
<span class="k">def</span> <span class="nf">path_derivatives</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">path_derivatives</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
<span class="sd">&quot;&quot;&quot;Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first)&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="bp">self</span><span class="o">.</span><span class="n">_path_derivatives_4</span><span class="p">()</span>
@@ -1159,24 +1184,36 @@
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_path_derivatives_5_shared</span><span class="p">()</span>
<span class="n">directory</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_uuid</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="c1"># first char of uuid</span>
<span class="n">derivative_path</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_library_path</span><span class="p">)</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&quot;resources/derivatives/</span><span class="si">{</span><span class="n">directory</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="p">)</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">syndicated</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">saved_to_library</span><span class="p">:</span>
<span class="c1"># syndicated (&quot;Shared with you&quot;) photos not yet saved to library</span>
<span class="n">derivative_path</span> <span class="o">=</span> <span class="s2">&quot;scopes/syndication/resources/derivatives&quot;</span>
<span class="n">thumb_path</span> <span class="o">=</span> <span class="p">(</span>
<span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">derivative_path</span><span class="si">}</span><span class="s2">/masters/</span><span class="si">{</span><span class="n">directory</span><span class="si">}</span><span class="s2">/</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">_4_5005_c.jpeg&quot;</span>
<span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">derivative_path</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;resources/derivatives/</span><span class="si">{</span><span class="n">directory</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="n">thumb_path</span> <span class="o">=</span> <span class="p">(</span>
<span class="sa">f</span><span class="s2">&quot;resources/derivatives/masters/</span><span class="si">{</span><span class="n">directory</span><span class="si">}</span><span class="s2">/</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">_4_5005_c.jpeg&quot;</span>
<span class="p">)</span>
<span class="n">derivative_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">_db</span><span class="o">.</span><span class="n">_library_path</span><span class="p">)</span><span class="o">.</span><span class="n">joinpath</span><span class="p">(</span><span class="n">derivative_path</span><span class="p">)</span>
<span class="n">thumb_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">_db</span><span class="o">.</span><span class="n">_library_path</span><span class="p">)</span><span class="o">.</span><span class="n">joinpath</span><span class="p">(</span><span class="n">thumb_path</span><span class="p">)</span>
<span class="c1"># find all files that start with uuid in derivative path</span>
<span class="n">files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">derivative_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</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">*.*&quot;</span><span class="p">))</span>
<span class="c1"># previews may be missing from derivatives path</span>
<span class="c1"># there are what appear to be low res thumbnails in the &quot;masters&quot; subfolder</span>
<span class="n">thumb_path</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_library_path</span><span class="p">)</span>
<span class="o">/</span> <span class="sa">f</span><span class="s2">&quot;resources/derivatives/masters/</span><span class="si">{</span><span class="n">directory</span><span class="si">}</span><span class="s2">/</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">_4_5005_c.jpeg&quot;</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">thumb_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
<span class="n">files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">thumb_path</span><span class="p">)</span>
<span class="c1"># sort by file size, largest first</span>
<span class="n">files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">files</span><span class="p">,</span> <span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">f</span><span class="p">:</span> <span class="n">f</span><span class="o">.</span><span class="n">stat</span><span class="p">()</span><span class="o">.</span><span class="n">st_size</span><span class="p">)</span>
<span class="c1"># return list of filename but skip .THM files (these are actually low-res thumbnails in JPEG format but with .THM extension)</span>
<span class="n">derivatives</span> <span class="o">=</span> <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">files</span> <span class="k">if</span> <span class="n">filename</span><span class="o">.</span><span class="n">suffix</span> <span class="o">!=</span> <span class="s2">&quot;.THM&quot;</span><span class="p">]</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">isphoto</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">derivatives</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="ow">and</span> <span class="n">derivatives</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">&quot;.mov&quot;</span><span class="p">):</span>
<span class="c1"># ensure .mov is first in list as poster image could be larger than the movie preview</span>
<span class="n">derivatives</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">derivatives</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">derivatives</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">derivatives</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="k">return</span> <span class="n">derivatives</span>
@@ -1495,6 +1532,38 @@
<span class="bp">self</span><span class="o">.</span><span class="n">_search_info_normalized</span> <span class="o">=</span> <span class="n">SearchInfo</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">normalized</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_search_info_normalized</span>
<span class="nd">@cached_property</span>
<span class="k">def</span> <span class="nf">syndicated</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Return true if photo was shared via syndication (e.g. via Messages, etc.);</span>
<span class="sd"> these are photos that appear in &quot;Shared with you&quot; album.</span>
<span class="sd"> Photos 8+ only; returns None if not Photos 8+.</span>
<span class="sd"> &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">photos_version</span> <span class="o">&lt;</span> <span class="mi">8</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="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_syndication_uuid</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;syndication_identifier&quot;</span><span class="p">]</span>
<span class="ow">is</span> <span class="ow">not</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="k">return</span> <span class="kc">False</span>
<span class="nd">@cached_property</span>
<span class="k">def</span> <span class="nf">saved_to_library</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Return True if syndicated photo has been saved to library;</span>
<span class="sd"> returns False if photo is not syndicated or has not been saved to the library.</span>
<span class="sd"> Returns None if not Photos 8+.</span>
<span class="sd"> Syndicated photos are photos that appear in &quot;Shared with you&quot; album; Photos 8+ only.</span>
<span class="sd"> &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">photos_version</span> <span class="o">&lt;</span> <span class="mi">8</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">_db</span><span class="o">.</span><span class="n">_db_syndication_uuid</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;syndication_history&quot;</span><span class="p">]</span> <span class="o">!=</span> <span class="mi">0</span>
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">False</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">labels</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;returns list of labels applied to photo by Photos image categorization</span>
@@ -1695,6 +1764,8 @@
<span class="k">def</span> <span class="nf">_detected_text</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;detect text in photo, either from cached extended attribute or by attempting text detection&quot;&quot;&quot;</span>
<span class="n">assert_macos</span><span class="p">()</span>
<span class="n">path</span> <span class="o">=</span> <span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">path_edited</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">hasadjustments</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">path_edited</span> <span class="k">else</span> <span class="bp">self</span><span class="o">.</span><span class="n">path</span>
<span class="p">)</span>
@@ -1974,41 +2045,31 @@
<span class="p">}</span>
<span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">info</span><span class="p">,</span> <span class="n">sort_keys</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
<div class="viewcode-block" id="PhotoInfo.asdict"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoInfo.asdict">[docs]</a> <span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;return dict representation&quot;&quot;&quot;</span>
<div class="viewcode-block" id="PhotoInfo.asdict"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoInfo.asdict">[docs]</a> <span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">shallow</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
<span class="sd">&quot;&quot;&quot;Return dict representation of PhotoInfo object.</span>
<span class="sd"> Args:</span>
<span class="sd"> shallow: if True, return shallow representation (does not contain folder_info, person_info, etc.)</span>
<span class="sd"> Returns:</span>
<span class="sd"> dict representation of PhotoInfo object</span>
<span class="sd"> Note:</span>
<span class="sd"> The shallow representation is used internally by export as it contains only the subset of data needed for export.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">adjustments</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">adjustments</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">adjustments</span> <span class="k">else</span> <span class="p">{}</span>
<span class="n">album_info</span> <span class="o">=</span> <span class="p">[</span><span class="n">album</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">for</span> <span class="n">album</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">album_info</span><span class="p">]</span>
<span class="n">burst_album_info</span> <span class="o">=</span> <span class="p">[</span><span class="n">a</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">for</span> <span class="n">a</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_album_info</span><span class="p">]</span>
<span class="n">burst_photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">uuid</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_photos</span><span class="p">]</span>
<span class="n">comments</span> <span class="o">=</span> <span class="p">[</span><span class="n">comment</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">for</span> <span class="n">comment</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">comments</span><span class="p">]</span>
<span class="n">exif_info</span> <span class="o">=</span> <span class="n">dataclasses</span><span class="o">.</span><span class="n">asdict</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">exif_info</span><span class="p">)</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">exif_info</span> <span class="k">else</span> <span class="p">{}</span>
<span class="n">face_info</span> <span class="o">=</span> <span class="p">[</span><span class="n">face</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">for</span> <span class="n">face</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">face_info</span><span class="p">]</span>
<span class="n">folders</span> <span class="o">=</span> <span class="p">{</span><span class="n">album</span><span class="o">.</span><span class="n">title</span><span class="p">:</span> <span class="n">album</span><span class="o">.</span><span class="n">folder_names</span> <span class="k">for</span> <span class="n">album</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">album_info</span><span class="p">}</span>
<span class="n">import_info</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">import_info</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">import_info</span> <span class="k">else</span> <span class="p">{}</span>
<span class="n">likes</span> <span class="o">=</span> <span class="p">[</span><span class="n">like</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">for</span> <span class="n">like</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">likes</span><span class="p">]</span>
<span class="n">person_info</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">person_info</span><span class="p">]</span>
<span class="n">place</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">place</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">place</span> <span class="k">else</span> <span class="p">{}</span>
<span class="n">project_info</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_info</span><span class="p">]</span>
<span class="n">score</span> <span class="o">=</span> <span class="n">dataclasses</span><span class="o">.</span><span class="n">asdict</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">score</span><span class="p">)</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">score</span> <span class="k">else</span> <span class="p">{}</span>
<span class="n">search_info</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">search_info</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">search_info</span> <span class="k">else</span> <span class="p">{}</span>
<span class="n">search_info_normalized</span> <span class="o">=</span> <span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">search_info_normalized</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">search_info_normalized</span> <span class="k">else</span> <span class="p">{}</span>
<span class="p">)</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s2">&quot;adjustments&quot;</span><span class="p">:</span> <span class="n">adjustments</span><span class="p">,</span>
<span class="s2">&quot;album_info&quot;</span><span class="p">:</span> <span class="n">album_info</span><span class="p">,</span>
<span class="n">dict_data</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">&quot;albums&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">albums</span><span class="p">,</span>
<span class="s2">&quot;burst_album_info&quot;</span><span class="p">:</span> <span class="n">burst_album_info</span><span class="p">,</span>
<span class="s2">&quot;burst_albums&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_albums</span><span class="p">,</span>
<span class="s2">&quot;burst_default_pick&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_default_pick</span><span class="p">,</span>
<span class="s2">&quot;burst_key&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_key</span><span class="p">,</span>
<span class="s2">&quot;burst_photos&quot;</span><span class="p">:</span> <span class="n">burst_photos</span><span class="p">,</span>
<span class="s2">&quot;burst_selected&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_selected</span><span class="p">,</span>
<span class="s2">&quot;burst&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst</span><span class="p">,</span>
<span class="s2">&quot;cloud_guid&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">cloud_guid</span><span class="p">,</span>
<span class="s2">&quot;cloud_metadata&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">cloud_metadata</span><span class="p">,</span>
<span class="s2">&quot;cloud_owner_hashed_id&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">cloud_owner_hashed_id</span><span class="p">,</span>
<span class="s2">&quot;comments&quot;</span><span class="p">:</span> <span class="n">comments</span><span class="p">,</span>
<span class="s2">&quot;date_added&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">date_added</span><span class="p">,</span>
@@ -2028,7 +2089,6 @@
<span class="s2">&quot;hdr&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">hdr</span><span class="p">,</span>
<span class="s2">&quot;height&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">height</span><span class="p">,</span>
<span class="s2">&quot;hidden&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">hidden</span><span class="p">,</span>
<span class="s2">&quot;import_info&quot;</span><span class="p">:</span> <span class="n">import_info</span><span class="p">,</span>
<span class="s2">&quot;incloud&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">incloud</span><span class="p">,</span>
<span class="s2">&quot;intrash&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">intrash</span><span class="p">,</span>
<span class="s2">&quot;iscloudasset&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">iscloudasset</span><span class="p">,</span>
@@ -2038,7 +2098,6 @@
<span class="s2">&quot;israw&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">israw</span><span class="p">,</span>
<span class="s2">&quot;isreference&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">isreference</span><span class="p">,</span>
<span class="s2">&quot;keywords&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">keywords</span><span class="p">,</span>
<span class="s2">&quot;labels_normalized&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">labels_normalized</span><span class="p">,</span>
<span class="s2">&quot;labels&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">labels</span><span class="p">,</span>
<span class="s2">&quot;latitude&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_latitude</span><span class="p">,</span>
<span class="s2">&quot;library&quot;</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">_library_path</span><span class="p">,</span>
@@ -2054,22 +2113,17 @@
<span class="s2">&quot;original_width&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">original_width</span><span class="p">,</span>
<span class="s2">&quot;owner&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">owner</span><span class="p">,</span>
<span class="s2">&quot;panorama&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">panorama</span><span class="p">,</span>
<span class="s2">&quot;path_derivatives&quot;</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="s2">&quot;path_edited_live_photo&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">path_edited_live_photo</span><span class="p">,</span>
<span class="s2">&quot;path_edited&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">path_edited</span><span class="p">,</span>
<span class="s2">&quot;path_live_photo&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">path_live_photo</span><span class="p">,</span>
<span class="s2">&quot;path_raw&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">path_raw</span><span class="p">,</span>
<span class="s2">&quot;path&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">path</span><span class="p">,</span>
<span class="s2">&quot;person_info&quot;</span><span class="p">:</span> <span class="n">person_info</span><span class="p">,</span>
<span class="s2">&quot;persons&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">persons</span><span class="p">,</span>
<span class="s2">&quot;place&quot;</span><span class="p">:</span> <span class="n">place</span><span class="p">,</span>
<span class="s2">&quot;portrait&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">portrait</span><span class="p">,</span>
<span class="s2">&quot;project_info&quot;</span><span class="p">:</span> <span class="n">project_info</span><span class="p">,</span>
<span class="s2">&quot;raw_original&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">raw_original</span><span class="p">,</span>
<span class="s2">&quot;score&quot;</span><span class="p">:</span> <span class="n">score</span><span class="p">,</span>
<span class="s2">&quot;screenshot&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">screenshot</span><span class="p">,</span>
<span class="s2">&quot;search_info_normalized&quot;</span><span class="p">:</span> <span class="n">search_info_normalized</span><span class="p">,</span>
<span class="s2">&quot;search_info&quot;</span><span class="p">:</span> <span class="n">search_info</span><span class="p">,</span>
<span class="s2">&quot;selfie&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">selfie</span><span class="p">,</span>
<span class="s2">&quot;shared&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">shared</span><span class="p">,</span>
<span class="s2">&quot;slow_mo&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">slow_mo</span><span class="p">,</span>
@@ -2083,16 +2137,59 @@
<span class="s2">&quot;uuid&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
<span class="s2">&quot;visible&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">visible</span><span class="p">,</span>
<span class="s2">&quot;width&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">width</span><span class="p">,</span>
<span class="p">}</span></div>
<span class="p">}</span>
<div class="viewcode-block" id="PhotoInfo.json"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoInfo.json">[docs]</a> <span class="k">def</span> <span class="nf">json</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return JSON representation&quot;&quot;&quot;</span>
<span class="c1"># non-shallow keys</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">shallow</span><span class="p">:</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;album_info&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">album</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">for</span> <span class="n">album</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">album_info</span><span class="p">]</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;path_derivatives&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">path_derivatives</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;adjustments&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">adjustments</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">adjustments</span> <span class="k">else</span> <span class="p">{}</span>
<span class="p">)</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;burst_album_info&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">a</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">for</span> <span class="n">a</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_album_info</span><span class="p">]</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;burst_albums&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_albums</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;burst_default_pick&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_default_pick</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;burst_key&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_key</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;burst_photos&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">uuid</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_photos</span><span class="p">]</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;burst_selected&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_selected</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;cloud_metadata&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">cloud_metadata</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;import_info&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">import_info</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">import_info</span> <span class="k">else</span> <span class="p">{}</span>
<span class="p">)</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;labels_normalized&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">labels_normalized</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;person_info&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">person_info</span><span class="p">]</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;project_info&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_info</span><span class="p">]</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;search_info&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">search_info</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">search_info</span> <span class="k">else</span> <span class="p">{}</span>
<span class="p">)</span>
<span class="n">dict_data</span><span class="p">[</span><span class="s2">&quot;search_info_normalized&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">search_info_normalized</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">search_info_normalized</span>
<span class="k">else</span> <span class="p">{}</span>
<span class="p">)</span>
<span class="k">return</span> <span class="n">dict_data</span></div>
<div class="viewcode-block" id="PhotoInfo.json"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoInfo.json">[docs]</a> <span class="k">def</span> <span class="nf">json</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">indent</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">shallow</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Return JSON representation</span>
<span class="sd"> Args:</span>
<span class="sd"> indent: indent level for JSON, if None, no indent</span>
<span class="sd"> shallow: if True, return shallow JSON representation (does not contain folder_info, person_info, etc.)</span>
<span class="sd"> Returns:</span>
<span class="sd"> JSON string</span>
<span class="sd"> Note:</span>
<span class="sd"> The shallow representation is used internally by export as it contains only the subset of data needed for export.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">def</span> <span class="nf">default</span><span class="p">(</span><span class="n">o</span><span class="p">):</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">o</span><span class="p">,</span> <span class="p">(</span><span class="n">datetime</span><span class="o">.</span><span class="n">date</span><span class="p">,</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="p">)):</span>
<span class="k">return</span> <span class="n">o</span><span class="o">.</span><span class="n">isoformat</span><span class="p">()</span>
<span class="n">dict_data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span>
<span class="n">dict_data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">asdict</span><span class="p">(</span><span class="n">shallow</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> <span class="k">if</span> <span class="n">shallow</span> <span class="k">else</span> <span class="bp">self</span><span class="o">.</span><span class="n">asdict</span><span class="p">(</span><span class="n">shallow</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
<span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">dict_data</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="c1"># sort lists such as keywords so JSON is consistent</span>
<span class="c1"># but do not sort certain items like location</span>
@@ -2100,7 +2197,11 @@
<span class="k">continue</span>
<span class="k">if</span> <span class="n">v</span> <span class="ow">and</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="p">(</span><span class="nb">list</span><span class="p">,</span> <span class="nb">tuple</span><span class="p">))</span> <span class="ow">and</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">v</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nb">dict</span><span class="p">):</span>
<span class="n">dict_data</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">v</span><span class="p">:</span> <span class="n">v</span> <span class="k">if</span> <span class="n">v</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">dict_data</span><span class="p">,</span> <span class="n">sort_keys</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">default</span><span class="p">)</span></div>
<span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">dict_data</span><span class="p">,</span> <span class="n">sort_keys</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">default</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="n">indent</span><span class="p">)</span></div>
<div class="viewcode-block" id="PhotoInfo.tables"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoInfo.tables">[docs]</a> <span class="k">def</span> <span class="nf">tables</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">PhotoTables</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Return PhotoTables object to provide access database tables associated with this photo (Photos 5+)&quot;&quot;&quot;</span>
<span class="k">return</span> <span class="kc">None</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">_photos_ver</span> <span class="o">&lt;</span> <span class="mi">5</span> <span class="k">else</span> <span class="n">PhotoTables</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span></div>
<span class="k">def</span> <span class="nf">_json_hexdigest</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;JSON for use by hexdigest()&quot;&quot;&quot;</span>
@@ -2114,15 +2215,9 @@
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">o</span><span class="p">,</span> <span class="p">(</span><span class="n">datetime</span><span class="o">.</span><span class="n">date</span><span class="p">,</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="p">)):</span>
<span class="k">return</span> <span class="n">o</span><span class="o">.</span><span class="n">isoformat</span><span class="p">()</span>
<span class="n">dict_data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span>
<span class="n">dict_data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">asdict</span><span class="p">(</span><span class="n">shallow</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="p">[</span>
<span class="s2">&quot;album_info&quot;</span><span class="p">,</span>
<span class="s2">&quot;burst_album_info&quot;</span><span class="p">,</span>
<span class="s2">&quot;face_info&quot;</span><span class="p">,</span>
<span class="s2">&quot;person_info&quot;</span><span class="p">,</span>
<span class="s2">&quot;visible&quot;</span><span class="p">,</span>
<span class="p">]:</span>
<span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">&quot;face_info&quot;</span><span class="p">,</span> <span class="s2">&quot;visible&quot;</span><span class="p">]:</span>
<span class="k">del</span> <span class="n">dict_data</span><span class="p">[</span><span class="n">k</span><span class="p">]</span>
<span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">dict_data</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos.photosalbum - osxphotos 0.58.1 documentation</title>
<title>osxphotos.photosalbum - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.58.1 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.58.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -197,31 +197,55 @@
<h1>Source code for osxphotos.photosalbum</h1><div class="highlight"><pre>
<span></span><span class="sd">&quot;&quot;&quot; PhotosAlbum class to create an album in default Photos library and add photos to it &quot;&quot;&quot;</span>
<span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
<span class="kn">import</span> <span class="nn">unicodedata</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
<span class="kn">import</span> <span class="nn">photoscript</span>
<span class="kn">from</span> <span class="nn">more_itertools</span> <span class="kn">import</span> <span class="n">chunked</span>
<span class="kn">from</span> <span class="nn">photoscript</span> <span class="kn">import</span> <span class="n">Album</span><span class="p">,</span> <span class="n">Folder</span><span class="p">,</span> <span class="n">Photo</span><span class="p">,</span> <span class="n">PhotosLibrary</span>
<span class="kn">from</span> <span class="nn">.photoinfo</span> <span class="kn">import</span> <span class="n">PhotoInfo</span>
<span class="kn">from</span> <span class="nn">.platform</span> <span class="kn">import</span> <span class="n">assert_macos</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">noop</span><span class="p">,</span> <span class="n">pluralize</span>
<span class="n">assert_macos</span><span class="p">()</span>
<span class="kn">import</span> <span class="nn">photoscript</span>
<span class="kn">from</span> <span class="nn">photoscript</span> <span class="kn">import</span> <span class="n">Album</span><span class="p">,</span> <span class="n">Folder</span><span class="p">,</span> <span class="n">Photo</span><span class="p">,</span> <span class="n">PhotosLibrary</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;PhotosAlbum&quot;</span><span class="p">,</span> <span class="s2">&quot;PhotosAlbumPhotoScript&quot;</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">get_unicode_variants</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
<span class="sd">&quot;&quot;&quot;Get all unicode variants of string&quot;&quot;&quot;</span>
<span class="n">variants</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">form</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">&quot;NFC&quot;</span><span class="p">,</span> <span class="s2">&quot;NFD&quot;</span><span class="p">,</span> <span class="s2">&quot;NFKC&quot;</span><span class="p">,</span> <span class="s2">&quot;NFKD&quot;</span><span class="p">]:</span>
<span class="n">normalized</span> <span class="o">=</span> <span class="n">unicodedata</span><span class="o">.</span><span class="n">normalize</span><span class="p">(</span><span class="n">form</span><span class="p">,</span> <span class="n">s</span><span class="p">)</span>
<span class="n">variants</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">normalized</span><span class="p">)</span>
<span class="k">return</span> <span class="n">variants</span>
<span class="k">def</span> <span class="nf">folder_by_path</span><span class="p">(</span><span class="n">folders</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span> <span class="n">verbose</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">callable</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Folder</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Get (and create if necessary) a Photos Folder by path (passed as list of folder names)&quot;&quot;&quot;</span>
<span class="n">library</span> <span class="o">=</span> <span class="n">PhotosLibrary</span><span class="p">()</span>
<span class="n">verbose</span> <span class="o">=</span> <span class="n">verbose</span> <span class="ow">or</span> <span class="n">noop</span>
<span class="n">top_folder_name</span> <span class="o">=</span> <span class="n">folders</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">top_folder</span> <span class="o">=</span> <span class="n">library</span><span class="o">.</span><span class="n">folder</span><span class="p">(</span><span class="n">top_folder_name</span><span class="p">,</span> <span class="n">top_level</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">top_folder</span><span class="p">:</span>
<span class="k">for</span> <span class="n">folder_variant</span> <span class="ow">in</span> <span class="n">get_unicode_variants</span><span class="p">(</span><span class="n">top_folder_name</span><span class="p">):</span>
<span class="n">top_folder</span> <span class="o">=</span> <span class="n">library</span><span class="o">.</span><span class="n">folder</span><span class="p">(</span><span class="n">folder_variant</span><span class="p">,</span> <span class="n">top_level</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">if</span> <span class="n">top_folder</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">break</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Creating folder &#39;</span><span class="si">{</span><span class="n">top_folder_name</span><span class="si">}</span><span class="s2">&#39;&quot;</span><span class="p">)</span>
<span class="n">top_folder</span> <span class="o">=</span> <span class="n">library</span><span class="o">.</span><span class="n">create_folder</span><span class="p">(</span><span class="n">top_folder_name</span><span class="p">)</span>
<span class="n">current_folder</span> <span class="o">=</span> <span class="n">top_folder</span>
<span class="k">for</span> <span class="n">folder_name</span> <span class="ow">in</span> <span class="n">folders</span><span class="p">:</span>
<span class="n">folder</span> <span class="o">=</span> <span class="n">current_folder</span><span class="o">.</span><span class="n">folder</span><span class="p">(</span><span class="n">folder_name</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">folder</span><span class="p">:</span>
<span class="k">for</span> <span class="n">folder_variant</span> <span class="ow">in</span> <span class="n">get_unicode_variants</span><span class="p">(</span><span class="n">folder_name</span><span class="p">):</span>
<span class="n">folder</span> <span class="o">=</span> <span class="n">current_folder</span><span class="o">.</span><span class="n">folder</span><span class="p">(</span><span class="n">folder_variant</span><span class="p">)</span>
<span class="k">if</span> <span class="n">folder</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">break</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Creating folder &#39;</span><span class="si">{</span><span class="n">folder_name</span><span class="si">}</span><span class="s2">&#39;&quot;</span><span class="p">)</span>
<span class="n">folder</span> <span class="o">=</span> <span class="n">current_folder</span><span class="o">.</span><span class="n">create_folder</span><span class="p">(</span><span class="n">folder_name</span><span class="p">)</span>
<span class="n">current_folder</span> <span class="o">=</span> <span class="n">folder</span>
@@ -238,15 +262,24 @@
<span class="c1"># have folders</span>
<span class="n">album_name</span> <span class="o">=</span> <span class="n">folders_album</span><span class="o">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">folder</span> <span class="o">=</span> <span class="n">folder_by_path</span><span class="p">(</span><span class="n">folders_album</span><span class="p">,</span> <span class="n">verbose</span><span class="p">)</span>
<span class="n">album</span> <span class="o">=</span> <span class="n">folder</span><span class="o">.</span><span class="n">album</span><span class="p">(</span><span class="n">album_name</span><span class="p">)</span>
<span class="k">if</span> <span class="n">album</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">for</span> <span class="n">album_variant</span> <span class="ow">in</span> <span class="n">get_unicode_variants</span><span class="p">(</span><span class="n">album_name</span><span class="p">):</span>
<span class="c1"># Get album if it exists</span>
<span class="c1"># need to check every unicode variant to avoid creating duplicate albums with same visual representation (#1085)</span>
<span class="n">album</span> <span class="o">=</span> <span class="n">folder</span><span class="o">.</span><span class="n">album</span><span class="p">(</span><span class="n">album_variant</span><span class="p">)</span>
<span class="k">if</span> <span class="n">album</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">break</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Creating album &#39;</span><span class="si">{</span><span class="n">album_name</span><span class="si">}</span><span class="s2">&#39;&quot;</span><span class="p">)</span>
<span class="n">album</span> <span class="o">=</span> <span class="n">folder</span><span class="o">.</span><span class="n">create_album</span><span class="p">(</span><span class="n">album_name</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># only have album name</span>
<span class="n">album_name</span> <span class="o">=</span> <span class="n">folders_album</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">album</span> <span class="o">=</span> <span class="n">library</span><span class="o">.</span><span class="n">album</span><span class="p">(</span><span class="n">album_name</span><span class="p">,</span> <span class="n">top_level</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">if</span> <span class="n">album</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">for</span> <span class="n">album_variant</span> <span class="ow">in</span> <span class="n">get_unicode_variants</span><span class="p">(</span><span class="n">album_name</span><span class="p">):</span>
<span class="n">album</span> <span class="o">=</span> <span class="n">library</span><span class="o">.</span><span class="n">album</span><span class="p">(</span><span class="n">album_variant</span><span class="p">,</span> <span class="n">top_level</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">if</span> <span class="n">album</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">break</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># album doesn&#39;t exist, create it</span>
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Creating album &#39;</span><span class="si">{</span><span class="n">album_name</span><span class="si">}</span><span class="s2">&#39;&quot;</span><span class="p">)</span>
<span class="n">album</span> <span class="o">=</span> <span class="n">library</span><span class="o">.</span><span class="n">create_album</span><span class="p">(</span><span class="n">album_name</span><span class="p">)</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../../genindex.html" /><link rel="search" title="Search" href="../../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos.photosdb._photosdb_process_comments - osxphotos 0.58.1 documentation</title>
<title>osxphotos.photosdb._photosdb_process_comments - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../../index.html"><div class="brand">osxphotos 0.58.1 documentation</div></a>
<a href="../../../index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../../index.html">
<span class="sidebar-brand-text">osxphotos 0.58.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -203,8 +203,8 @@
<span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
<span class="kn">from</span> <span class="nn">.._constants</span> <span class="kn">import</span> <span class="n">_DB_TABLE_NAMES</span><span class="p">,</span> <span class="n">_PHOTOS_4_VERSION</span><span class="p">,</span> <span class="n">TIME_DELTA</span>
<span class="kn">from</span> <span class="nn">..utils</span> <span class="kn">import</span> <span class="n">normalize_unicode</span>
<span class="kn">from</span> <span class="nn">..sqlite_utils</span> <span class="kn">import</span> <span class="n">sqlite_open_ro</span>
<span class="kn">from</span> <span class="nn">..unicode</span> <span class="kn">import</span> <span class="n">normalize_unicode</span>
<span class="k">def</span> <span class="nf">_process_comments</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../../genindex.html" /><link rel="search" title="Search" href="../../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos.photosdb.photosdb - osxphotos 0.59.0 documentation</title>
<title>osxphotos.photosdb.photosdb - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../../index.html"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="../../../index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../../index.html">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -218,7 +218,6 @@
<span class="kn">from</span> <span class="nn">unicodedata</span> <span class="kn">import</span> <span class="n">normalize</span>
<span class="kn">import</span> <span class="nn">bitmath</span>
<span class="kn">import</span> <span class="nn">photoscript</span>
<span class="kn">from</span> <span class="nn">rich</span> <span class="kn">import</span> <span class="nb">print</span>
<span class="kn">from</span> <span class="nn">.._constants</span> <span class="kn">import</span> <span class="p">(</span>
@@ -254,18 +253,17 @@
<span class="kn">from</span> <span class="nn">..personinfo</span> <span class="kn">import</span> <span class="n">PersonInfo</span>
<span class="kn">from</span> <span class="nn">..photoinfo</span> <span class="kn">import</span> <span class="n">PhotoInfo</span>
<span class="kn">from</span> <span class="nn">..phototemplate</span> <span class="kn">import</span> <span class="n">RenderOptions</span>
<span class="kn">from</span> <span class="nn">..platform</span> <span class="kn">import</span> <span class="n">get_macos_version</span><span class="p">,</span> <span class="n">is_macos</span>
<span class="kn">from</span> <span class="nn">..queryoptions</span> <span class="kn">import</span> <span class="n">QueryOptions</span>
<span class="kn">from</span> <span class="nn">..rich_utils</span> <span class="kn">import</span> <span class="n">add_rich_markup_tag</span>
<span class="kn">from</span> <span class="nn">..sqlite_utils</span> <span class="kn">import</span> <span class="n">sqlite_db_is_locked</span><span class="p">,</span> <span class="n">sqlite_open_ro</span>
<span class="kn">from</span> <span class="nn">..utils</span> <span class="kn">import</span> <span class="p">(</span>
<span class="n">_check_file_exists</span><span class="p">,</span>
<span class="n">get_macos_version</span><span class="p">,</span>
<span class="n">get_last_library_path</span><span class="p">,</span>
<span class="n">noop</span><span class="p">,</span>
<span class="n">normalize_unicode</span><span class="p">,</span>
<span class="p">)</span>
<span class="kn">from</span> <span class="nn">..unicode</span> <span class="kn">import</span> <span class="n">normalize_unicode</span>
<span class="kn">from</span> <span class="nn">..utils</span> <span class="kn">import</span> <span class="n">_check_file_exists</span><span class="p">,</span> <span class="n">get_last_library_path</span><span class="p">,</span> <span class="n">noop</span>
<span class="kn">from</span> <span class="nn">.photosdb_utils</span> <span class="kn">import</span> <span class="n">get_db_model_version</span><span class="p">,</span> <span class="n">get_db_version</span>
<span class="k">if</span> <span class="n">is_macos</span><span class="p">:</span>
<span class="kn">import</span> <span class="nn">photoscript</span>
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s2">&quot;osxphotos&quot;</span><span class="p">)</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;PhotosDB&quot;</span><span class="p">]</span>
@@ -290,6 +288,7 @@
<span class="n">labels_normalized</span><span class="p">,</span>
<span class="n">labels_normalized_as_dict</span><span class="p">,</span>
<span class="p">)</span>
<span class="kn">from</span> <span class="nn">._photosdb_process_syndicationinfo</span> <span class="kn">import</span> <span class="n">_process_syndicationinfo</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span>
@@ -315,8 +314,8 @@
<span class="c1"># Check OS version</span>
<span class="n">system</span> <span class="o">=</span> <span class="n">platform</span><span class="o">.</span><span class="n">system</span><span class="p">()</span>
<span class="p">(</span><span class="n">ver</span><span class="p">,</span> <span class="n">major</span><span class="p">,</span> <span class="n">_</span><span class="p">)</span> <span class="o">=</span> <span class="n">get_macos_version</span><span class="p">()</span>
<span class="k">if</span> <span class="n">system</span> <span class="o">!=</span> <span class="s2">&quot;Darwin&quot;</span> <span class="ow">or</span> <span class="p">((</span><span class="n">ver</span><span class="p">,</span> <span class="n">major</span><span class="p">)</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">_TESTED_OS_VERSIONS</span><span class="p">):</span>
<span class="p">(</span><span class="n">ver</span><span class="p">,</span> <span class="n">major</span><span class="p">,</span> <span class="n">_</span><span class="p">)</span> <span class="o">=</span> <span class="n">get_macos_version</span><span class="p">()</span> <span class="k">if</span> <span class="n">is_macos</span> <span class="k">else</span> <span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="kc">None</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
<span class="k">if</span> <span class="n">system</span> <span class="o">==</span> <span class="s2">&quot;Darwin&quot;</span> <span class="ow">and</span> <span class="p">((</span><span class="n">ver</span><span class="p">,</span> <span class="n">major</span><span class="p">)</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">_TESTED_OS_VERSIONS</span><span class="p">):</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">&quot;WARNING: This module has only been tested with macOS versions &quot;</span>
<span class="sa">f</span><span class="s2">&quot;[</span><span class="si">{</span><span class="s1">&#39;, &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">v</span><span class="si">}</span><span class="s1">.</span><span class="si">{</span><span class="n">m</span><span class="si">}</span><span class="s1">&#39;</span> <span class="k">for</span> <span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">m</span><span class="p">)</span> <span class="ow">in</span> <span class="n">_TESTED_OS_VERSIONS</span><span class="p">)</span><span class="si">}</span><span class="s2">]: &quot;</span>
@@ -484,6 +483,10 @@
<span class="c1"># Dict to hold data on imports for Photos &lt;= 4</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_db_import_group</span> <span class="o">=</span> <span class="p">{}</span>
<span class="c1"># Dict to hold syndication info for Photos &gt;= 8</span>
<span class="c1"># key is UUID and value is dict of syndication info</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_db_syndication_uuid</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;dbfile = </span><span class="si">{</span><span class="n">dbfile</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="n">dbfile</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
@@ -713,7 +716,8 @@
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">album_info_shared</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;return list of AlbumInfo objects for each shared album in the photos database</span>
<span class="sd"> only valid for Photos 5; on Photos &lt;= 4, prints warning and returns empty list&quot;&quot;&quot;</span>
<span class="sd"> only valid for Photos 5; on Photos &lt;= 4, prints warning and returns empty list</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="c1"># if _dbalbum_details[key][&quot;cloudownerhashedpersonid&quot;] is not None, then it&#39;s a shared album</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">_album_info_shared</span>
@@ -740,7 +744,8 @@
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">albums_shared</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;return list of shared albums found in photos database</span>
<span class="sd"> only valid for Photos 5; on Photos &lt;= 4, prints warning and returns empty list&quot;&quot;&quot;</span>
<span class="sd"> only valid for Photos 5; on Photos &lt;= 4, prints warning and returns empty list</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="c1"># Could be more than one album with same name</span>
<span class="c1"># Right now, they are treated as same album and photos are combined from albums with same name</span>
@@ -2709,6 +2714,10 @@
<span class="n">verbose</span><span class="p">(</span><span class="s2">&quot;Processing moments.&quot;</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process_moments</span><span class="p">()</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">photos_version</span> <span class="o">&gt;=</span> <span class="mi">8</span><span class="p">:</span>
<span class="n">verbose</span><span class="p">(</span><span class="s2">&quot;Processing syndication info.&quot;</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process_syndicationinfo</span><span class="p">()</span>
<span class="n">verbose</span><span class="p">(</span><span class="s2">&quot;Done processing details from Photos library.&quot;</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_process_moments</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
@@ -3250,7 +3259,6 @@
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">None</span></div>
<span class="c1"># TODO: add to docs and test</span>
<div class="viewcode-block" id="PhotosDB.photos_by_uuid"><a class="viewcode-back" href="../../../reference.html#osxphotos.PhotosDB.photos_by_uuid">[docs]</a> <span class="k">def</span> <span class="nf">photos_by_uuid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuids</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Returns a list of photos with UUID in uuids.</span>
<span class="sd"> Does not generate error if invalid or missing UUID passed.</span>
@@ -3263,14 +3271,11 @@
<span class="sd"> Returns:</span>
<span class="sd"> list of PhotoInfo instance for photo with UUID matching uuid or [] if no match</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">uuid</span> <span class="ow">in</span> <span class="n">uuids</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">photos</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">PhotoInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="o">=</span><span class="n">uuid</span><span class="p">,</span> <span class="n">info</span><span class="o">=</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="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
<span class="c1"># ignore missing/invlaid UUID</span>
<span class="k">pass</span>
<span class="k">return</span> <span class="n">photos</span></div>
<span class="k">return</span> <span class="p">[</span>
<span class="n">PhotoInfo</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="o">=</span><span class="n">uuid</span><span class="p">,</span> <span class="n">info</span><span class="o">=</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="k">for</span> <span class="n">uuid</span> <span class="ow">in</span> <span class="n">uuids</span>
<span class="k">if</span> <span class="n">uuid</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span>
<span class="p">]</span></div>
<div class="viewcode-block" id="PhotosDB.query"><a class="viewcode-back" href="../../../reference.html#osxphotos.PhotosDB.query">[docs]</a> <span class="k">def</span> <span class="nf">query</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="n">QueryOptions</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">PhotoInfo</span><span class="p">]:</span>
<span class="sd">&quot;&quot;&quot;Run a query against PhotosDB to extract the photos based on user supplied options</span>
@@ -3712,6 +3717,16 @@
<span class="n">added_after</span> <span class="o">=</span> <span class="n">datetime_naive_to_local</span><span class="p">(</span><span class="n">added_after</span><span class="p">)</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">date_added</span> <span class="ow">and</span> <span class="n">p</span><span class="o">.</span><span class="n">date_added</span> <span class="o">&gt;</span> <span class="n">added_after</span><span class="p">]</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">syndicated</span><span class="p">:</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">syndicated</span><span class="p">]</span>
<span class="k">elif</span> <span class="n">options</span><span class="o">.</span><span class="n">not_syndicated</span><span class="p">:</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">p</span><span class="o">.</span><span class="n">syndicated</span><span class="p">]</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">saved_to_library</span><span class="p">:</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">syndicated</span> <span class="ow">and</span> <span class="n">p</span><span class="o">.</span><span class="n">saved_to_library</span><span class="p">]</span>
<span class="k">elif</span> <span class="n">options</span><span class="o">.</span><span class="n">not_saved_to_library</span><span class="p">:</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">syndicated</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">p</span><span class="o">.</span><span class="n">saved_to_library</span><span class="p">]</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">function</span><span class="p">:</span>
<span class="k">for</span> <span class="n">function</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">function</span><span class="p">:</span>
<span class="n">photos</span> <span class="o">=</span> <span class="n">function</span><span class="p">[</span><span class="mi">0</span><span class="p">](</span><span class="n">photos</span><span class="p">)</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos.phototemplate - osxphotos 0.59.0 documentation</title>
<title>osxphotos.phototemplate - osxphotos 0.60.0 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.60.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -218,7 +218,6 @@
<span class="kn">from</span> <span class="nn">.datetime_formatter</span> <span class="kn">import</span> <span class="n">DateTimeFormatter</span>
<span class="kn">from</span> <span class="nn">.exiftool</span> <span class="kn">import</span> <span class="n">ExifToolCaching</span>
<span class="kn">from</span> <span class="nn">.path_utils</span> <span class="kn">import</span> <span class="n">sanitize_dirname</span><span class="p">,</span> <span class="n">sanitize_filename</span><span class="p">,</span> <span class="n">sanitize_pathpart</span>
<span class="kn">from</span> <span class="nn">.text_detection</span> <span class="kn">import</span> <span class="n">detect_text</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">expand_and_validate_filepath</span><span class="p">,</span> <span class="n">load_function</span><span class="p">,</span> <span class="n">uuid_to_shortuuid</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span>
@@ -1350,7 +1349,7 @@
<span class="p">)</span>
<span class="k">except</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">&quot;comparison operators may only be used with values that can be converted to numbers: </span><span class="si">{</span><span class="n">vals</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">conditional_value</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="sa">f</span><span class="s2">&quot;comparison operators may only be used with values that can be converted to numbers: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">conditional_value</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="kc">False</span>
@@ -1879,7 +1878,7 @@
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Unhandled template value: </span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
<span class="k">def</span> <span class="nf">get_place_value</span><span class="p">(</span><span class="n">photo</span><span class="p">:</span> <span class="s2">&quot;PhotoInfo&quot;</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">get_place_value</span><span class="p">(</span><span class="n">photo</span><span class="p">:</span> <span class="s2">&quot;PhotoInfo&quot;</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span> <span class="c1"># noqa: F821</span>
<span class="sd">&quot;&quot;&quot;Get the value of a &#39;place&#39; field by attribute</span>
<span class="sd"> Args:</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos.placeinfo - osxphotos 0.59.0 documentation</title>
<title>osxphotos.placeinfo - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -208,8 +208,7 @@
<span class="kn">import</span> <span class="nn">yaml</span>
<span class="kn">from</span> <span class="nn">bpylist2</span> <span class="kn">import</span> <span class="n">archiver</span>
<span class="kn">from</span> <span class="nn">._constants</span> <span class="kn">import</span> <span class="n">UNICODE_FORMAT</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">normalize_unicode</span>
<span class="kn">from</span> <span class="nn">.unicode</span> <span class="kn">import</span> <span class="n">normalize_unicode</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span>
<span class="s2">&quot;PLRevGeoLocationInfo&quot;</span><span class="p">,</span>
@@ -268,6 +267,7 @@
<span class="p">],</span>
<span class="p">)</span>
<span class="c1"># The following classes represent Photo Library Reverse Geolocation Info as stored</span>
<span class="c1"># in ZADDITIONALASSETATTRIBUTES.ZREVERSELOCATIONDATA</span>
<span class="c1"># These classes are used by bpylist.archiver to unarchive the serialized objects</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos.queryoptions - osxphotos 0.58.1 documentation</title>
<title>osxphotos.queryoptions - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.58.1 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.58.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -305,6 +305,10 @@
<span class="sd"> uti: list of UTIs to search for</span>
<span class="sd"> uuid: list of uuids to search for</span>
<span class="sd"> year: search for photos taken in a given year</span>
<span class="sd"> syndicated: search for photos that have been shared via syndication (&quot;Shared with You&quot; album via Messages, etc.)</span>
<span class="sd"> not_syndicated: search for photos that have not been shared via syndication (&quot;Shared with You&quot; album via Messages, etc.)</span>
<span class="sd"> saved_to_library: search for syndicated photos that have been saved to the Photos library</span>
<span class="sd"> not_saved_to_library: search for syndicated photos that have not been saved to the Photos library</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">added_after</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
@@ -389,6 +393,10 @@
<span class="n">uti</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">uuid</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">year</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">int</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">syndicated</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_syndicated</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">saved_to_library</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_saved_to_library</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="n">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span></div>
@@ -458,6 +466,8 @@
<span class="p">(</span><span class="s2">&quot;deleted&quot;</span><span class="p">,</span> <span class="s2">&quot;not_deleted&quot;</span><span class="p">),</span>
<span class="p">(</span><span class="s2">&quot;deleted&quot;</span><span class="p">,</span> <span class="s2">&quot;deleted_only&quot;</span><span class="p">),</span>
<span class="p">(</span><span class="s2">&quot;deleted_only&quot;</span><span class="p">,</span> <span class="s2">&quot;not_deleted&quot;</span><span class="p">),</span>
<span class="p">(</span><span class="s2">&quot;syndicated&quot;</span><span class="p">,</span> <span class="s2">&quot;not_syndicated&quot;</span><span class="p">),</span>
<span class="p">(</span><span class="s2">&quot;saved_to_library&quot;</span><span class="p">,</span> <span class="s2">&quot;not_saved_to_library&quot;</span><span class="p">),</span>
<span class="p">]</span>
<span class="c1"># TODO: add option to validate requiring at least one query arg</span>
<span class="k">for</span> <span class="n">arg</span><span class="p">,</span> <span class="n">not_arg</span> <span class="ow">in</span> <span class="n">exclusive</span><span class="p">:</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos.scoreinfo - osxphotos 0.58.1 documentation</title>
<title>osxphotos.scoreinfo - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.58.1 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.58.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -197,7 +197,7 @@
<h1>Source code for osxphotos.scoreinfo</h1><div class="highlight"><pre>
<span></span><span class="sd">&quot;&quot;&quot; ScoreInfo class to expose computed score info from the library &quot;&quot;&quot;</span>
<span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">asdict</span>
<span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">asdict</span><span class="p">,</span> <span class="n">dataclass</span>
<span class="kn">from</span> <span class="nn">._constants</span> <span class="kn">import</span> <span class="n">_PHOTOS_4_VERSION</span>

View File

@@ -361,7 +361,7 @@ Template Substitutions
* - {tab}
- :A tab: '\t'
* - {osxphotos_version}
- The osxphotos version, e.g. '0.59.0'
- The osxphotos version, e.g. '0.60.5'
* - {osxphotos_cmd_line}
- The full command line used to run osxphotos
* - {album}

View File

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

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Template System" href="template_help.html" /><link rel="prev" title="OSXPhotos Tutorial" href="tutorial.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>OSXPhotos Command Line Interface (CLI) - osxphotos 0.59.0 documentation</title>
<title>OSXPhotos Command Line Interface (CLI) - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -728,6 +728,30 @@ See <cite>osxphotos help timewarp</cite> for more information.</p>
<dd><p>Search for photos that are not in iCloud (have not been synched)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-add-locations-syndicated">
<span class="sig-name descname"><span class="pre">--syndicated</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-add-locations-syndicated" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that have been shared via syndication (Shared with You album via Messages, etc.)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-add-locations-not-syndicated">
<span class="sig-name descname"><span class="pre">--not-syndicated</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-add-locations-not-syndicated" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that have not been shared via syndication (Shared with You album via Messages, etc.)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-add-locations-saved-to-library">
<span class="sig-name descname"><span class="pre">--saved-to-library</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-add-locations-saved-to-library" title="Permalink to this definition">#</a></dt>
<dd><p>Search for syndicated photos that have saved to the library</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-add-locations-not-saved-to-library">
<span class="sig-name descname"><span class="pre">--not-saved-to-library</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-add-locations-not-saved-to-library" title="Permalink to this definition">#</a></dt>
<dd><p>Search for syndicated photos that have not saved to the library</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-add-locations-regex">
<span class="sig-name descname"><span class="pre">--regex</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;REGEX</span> <span class="pre">TEMPLATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-add-locations-regex" title="Permalink to this definition">#</a></dt>
@@ -1659,6 +1683,30 @@ to modify this behavior.</p>
<dd><p>Search for photos that are not in iCloud (have not been synched)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-syndicated">
<span class="sig-name descname"><span class="pre">--syndicated</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-export-syndicated" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that have been shared via syndication (Shared with You album via Messages, etc.)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-not-syndicated">
<span class="sig-name descname"><span class="pre">--not-syndicated</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-export-not-syndicated" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that have not been shared via syndication (Shared with You album via Messages, etc.)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-saved-to-library">
<span class="sig-name descname"><span class="pre">--saved-to-library</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-export-saved-to-library" title="Permalink to this definition">#</a></dt>
<dd><p>Search for syndicated photos that have saved to the library</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-not-saved-to-library">
<span class="sig-name descname"><span class="pre">--not-saved-to-library</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-export-not-saved-to-library" title="Permalink to this definition">#</a></dt>
<dd><p>Search for syndicated photos that have not saved to the library</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-regex">
<span class="sig-name descname"><span class="pre">--regex</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;REGEX</span> <span class="pre">TEMPLATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-export-regex" title="Permalink to this definition">#</a></dt>
@@ -2781,6 +2829,12 @@ If the same query option is provided multiple times, they are treated as
<dd><p>Print output in JSON format.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-count">
<span class="sig-name descname"><span class="pre">--count</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-query-count" title="Permalink to this definition">#</a></dt>
<dd><p>Print count of photos matching query and exit.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-keyword">
<span class="sig-name descname"><span class="pre">--keyword</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;KEYWORD&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-query-keyword" title="Permalink to this definition">#</a></dt>
@@ -3225,6 +3279,30 @@ If the same query option is provided multiple times, they are treated as
<dd><p>Search for photos that are not in iCloud (have not been synched)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-syndicated">
<span class="sig-name descname"><span class="pre">--syndicated</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-query-syndicated" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that have been shared via syndication (Shared with You album via Messages, etc.)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-not-syndicated">
<span class="sig-name descname"><span class="pre">--not-syndicated</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-query-not-syndicated" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that have not been shared via syndication (Shared with You album via Messages, etc.)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-saved-to-library">
<span class="sig-name descname"><span class="pre">--saved-to-library</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-query-saved-to-library" title="Permalink to this definition">#</a></dt>
<dd><p>Search for syndicated photos that have saved to the library</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-not-saved-to-library">
<span class="sig-name descname"><span class="pre">--not-saved-to-library</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-query-not-saved-to-library" title="Permalink to this definition">#</a></dt>
<dd><p>Search for syndicated photos that have not saved to the library</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-regex">
<span class="sig-name descname"><span class="pre">--regex</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;REGEX</span> <span class="pre">TEMPLATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-query-regex" title="Permalink to this definition">#</a></dt>
@@ -3762,6 +3840,30 @@ If the same query option is provided multiple times, they are treated as
<dd><p>Search for photos that are not in iCloud (have not been synched)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-syndicated">
<span class="sig-name descname"><span class="pre">--syndicated</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-repl-syndicated" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that have been shared via syndication (Shared with You album via Messages, etc.)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-not-syndicated">
<span class="sig-name descname"><span class="pre">--not-syndicated</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-repl-not-syndicated" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that have not been shared via syndication (Shared with You album via Messages, etc.)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-saved-to-library">
<span class="sig-name descname"><span class="pre">--saved-to-library</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-repl-saved-to-library" title="Permalink to this definition">#</a></dt>
<dd><p>Search for syndicated photos that have saved to the library</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-not-saved-to-library">
<span class="sig-name descname"><span class="pre">--not-saved-to-library</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-repl-not-saved-to-library" title="Permalink to this definition">#</a></dt>
<dd><p>Search for syndicated photos that have not saved to the library</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-regex">
<span class="sig-name descname"><span class="pre">--regex</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;REGEX</span> <span class="pre">TEMPLATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-repl-regex" title="Permalink to this definition">#</a></dt>
@@ -4421,6 +4523,30 @@ two different computers, you can export the metadata to a shared folder.</p>
<dd><p>Search for photos that are not in iCloud (have not been synched)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-sync-syndicated">
<span class="sig-name descname"><span class="pre">--syndicated</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-sync-syndicated" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that have been shared via syndication (Shared with You album via Messages, etc.)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-sync-not-syndicated">
<span class="sig-name descname"><span class="pre">--not-syndicated</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-sync-not-syndicated" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that have not been shared via syndication (Shared with You album via Messages, etc.)</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-sync-saved-to-library">
<span class="sig-name descname"><span class="pre">--saved-to-library</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-sync-saved-to-library" title="Permalink to this definition">#</a></dt>
<dd><p>Search for syndicated photos that have saved to the library</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-sync-not-saved-to-library">
<span class="sig-name descname"><span class="pre">--not-saved-to-library</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-sync-not-saved-to-library" title="Permalink to this definition">#</a></dt>
<dd><p>Search for syndicated photos that have not saved to the library</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-sync-regex">
<span class="sig-name descname"><span class="pre">--regex</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;REGEX</span> <span class="pre">TEMPLATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-sync-regex" title="Permalink to this definition">#</a></dt>

View File

@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#" /><link rel="search" title="Search" href="search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/><title>Index - osxphotos 0.59.0 documentation</title>
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/><title>Index - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -122,7 +122,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -145,7 +145,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -423,6 +423,13 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-convert-to-jpeg">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
--count
<ul>
<li><a href="cli.html#cmdoption-osxphotos-query-count">osxphotos-query command line option</a>
</li>
</ul></li>
<li>
@@ -1563,8 +1570,6 @@
<li><a href="cli.html#cmdoption-osxphotos-sync-not-cloudasset">osxphotos-sync command line option</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
--not-edited
@@ -1595,6 +1600,8 @@
<li><a href="cli.html#cmdoption-osxphotos-sync-not-favorite">osxphotos-sync command line option</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
--not-hdr
@@ -1728,6 +1735,21 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-not-reference">osxphotos-repl command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-not-reference">osxphotos-sync command line option</a>
</li>
</ul></li>
<li>
--not-saved-to-library
<ul>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-not-saved-to-library">osxphotos-add-locations command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-not-saved-to-library">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-not-saved-to-library">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-not-saved-to-library">osxphotos-repl command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-not-saved-to-library">osxphotos-sync command line option</a>
</li>
</ul></li>
<li>
@@ -1786,6 +1808,21 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-not-slow-mo">osxphotos-repl command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-not-slow-mo">osxphotos-sync command line option</a>
</li>
</ul></li>
<li>
--not-syndicated
<ul>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-not-syndicated">osxphotos-add-locations command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-not-syndicated">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-not-syndicated">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-not-syndicated">osxphotos-repl command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-not-syndicated">osxphotos-sync command line option</a>
</li>
</ul></li>
<li>
@@ -2132,6 +2169,21 @@
<li><a href="cli.html#cmdoption-osxphotos-export-save-config">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-save-config">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
--saved-to-library
<ul>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-saved-to-library">osxphotos-add-locations command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-saved-to-library">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-saved-to-library">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-saved-to-library">osxphotos-repl command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-saved-to-library">osxphotos-sync command line option</a>
</li>
</ul></li>
<li>
@@ -2303,6 +2355,21 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-diff-s">osxphotos-diff command line option</a>
</li>
</ul></li>
<li>
--syndicated
<ul>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-syndicated">osxphotos-add-locations command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-syndicated">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-syndicated">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-syndicated">osxphotos-repl command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-syndicated">osxphotos-sync command line option</a>
</li>
</ul></li>
<li>
@@ -3305,10 +3372,10 @@
</ul></li>
<li><a href="reference.html#osxphotos.FileUtil">FileUtil (class in osxphotos)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.ExportOptions.fileutil">fileutil (osxphotos.ExportOptions attribute)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.FileUtilNoOp">FileUtilNoOp (class in osxphotos)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoTemplate.filter_predicate">filter_predicate() (osxphotos.PhotoTemplate method)</a>
@@ -3320,9 +3387,17 @@
<li><a href="reference.html#osxphotos.PhotosDB.folder_info">folder_info (osxphotos.PhotosDB property)</a>
</li>
<li><a href="reference.html#osxphotos.AlbumInfo.folder_list">folder_list (osxphotos.AlbumInfo property)</a>
<ul>
<li><a href="reference.html#osxphotos.ProjectInfo.folder_list">(osxphotos.ProjectInfo property)</a>
</li>
</ul></li>
<li><a href="reference.html#osxphotos.AlbumInfo.folder_names">folder_names (osxphotos.AlbumInfo property)</a>
<ul>
<li><a href="reference.html#osxphotos.ProjectInfo.folder_names">(osxphotos.ProjectInfo property)</a>
</li>
</ul></li>
<li><a href="reference.html#osxphotos.FolderInfo">FolderInfo (class in osxphotos)</a>
</li>
<li><a href="reference.html#osxphotos.PhotosDB.folders">folders (osxphotos.PhotosDB property)</a>
@@ -3656,10 +3731,10 @@
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_favorite">not_favorite (osxphotos.QueryOptions attribute)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.QueryOptions.not_hdr">not_hdr (osxphotos.QueryOptions attribute)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.QueryOptions.not_hidden">not_hidden (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_in_album">not_in_album (osxphotos.QueryOptions attribute)</a>
@@ -3675,6 +3750,8 @@
<li><a href="reference.html#osxphotos.QueryOptions.not_portrait">not_portrait (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_reference">not_reference (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_saved_to_library">not_saved_to_library (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_screenshot">not_screenshot (osxphotos.QueryOptions attribute)</a>
</li>
@@ -3683,6 +3760,8 @@
<li><a href="reference.html#osxphotos.QueryOptions.not_shared">not_shared (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_slow_mo">not_slow_mo (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_syndicated">not_syndicated (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_time_lapse">not_time_lapse (osxphotos.QueryOptions attribute)</a>
</li>
@@ -3837,6 +3916,8 @@
<li><a href="cli.html#cmdoption-osxphotos-add-locations-not-portrait">--not-portrait</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-not-reference">--not-reference</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-not-saved-to-library">--not-saved-to-library</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-not-screenshot">--not-screenshot</a>
</li>
@@ -3845,6 +3926,8 @@
<li><a href="cli.html#cmdoption-osxphotos-add-locations-not-shared">--not-shared</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-not-slow-mo">--not-slow-mo</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-not-syndicated">--not-syndicated</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-not-time-lapse">--not-time-lapse</a>
</li>
@@ -3865,6 +3948,8 @@
<li><a href="cli.html#cmdoption-osxphotos-add-locations-query-function">--query-function</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-regex">--regex</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-saved-to-library">--saved-to-library</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-screenshot">--screenshot</a>
</li>
@@ -3875,6 +3960,8 @@
<li><a href="cli.html#cmdoption-osxphotos-add-locations-shared">--shared</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-slow-mo">--slow-mo</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-syndicated">--syndicated</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-add-locations-theme">--theme</a>
</li>
@@ -4241,6 +4328,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-not-portrait">--not-portrait</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-not-reference">--not-reference</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-not-saved-to-library">--not-saved-to-library</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-not-screenshot">--not-screenshot</a>
</li>
@@ -4249,6 +4338,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-not-shared">--not-shared</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-not-slow-mo">--not-slow-mo</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-not-syndicated">--not-syndicated</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-not-time-lapse">--not-time-lapse</a>
</li>
@@ -4299,6 +4390,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-retry">--retry</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-save-config">--save-config</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-saved-to-library">--saved-to-library</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-screenshot">--screenshot</a>
</li>
@@ -4329,6 +4422,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-slow-mo">--slow-mo</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-strip">--strip</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-syndicated">--syndicated</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-theme">--theme</a>
</li>
@@ -4674,6 +4769,8 @@
<li><a href="cli.html#cmdoption-osxphotos-query-burst">--burst</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-cloudasset">--cloudasset</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-count">--count</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-library">--db</a>
</li>
@@ -4778,6 +4875,8 @@
<li><a href="cli.html#cmdoption-osxphotos-query-not-portrait">--not-portrait</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-not-reference">--not-reference</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-not-saved-to-library">--not-saved-to-library</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-not-screenshot">--not-screenshot</a>
</li>
@@ -4786,6 +4885,8 @@
<li><a href="cli.html#cmdoption-osxphotos-query-not-shared">--not-shared</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-not-slow-mo">--not-slow-mo</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-not-syndicated">--not-syndicated</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-not-time-lapse">--not-time-lapse</a>
</li>
@@ -4810,6 +4911,8 @@
<li><a href="cli.html#cmdoption-osxphotos-query-quiet">--quiet</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-regex">--regex</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-saved-to-library">--saved-to-library</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-screenshot">--screenshot</a>
</li>
@@ -4820,6 +4923,8 @@
<li><a href="cli.html#cmdoption-osxphotos-query-shared">--shared</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-slow-mo">--slow-mo</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-syndicated">--syndicated</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-time-lapse">--time-lapse</a>
</li>
@@ -4961,6 +5066,8 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-not-portrait">--not-portrait</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-not-reference">--not-reference</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-not-saved-to-library">--not-saved-to-library</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-not-screenshot">--not-screenshot</a>
</li>
@@ -4969,6 +5076,8 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-not-shared">--not-shared</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-not-slow-mo">--not-slow-mo</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-not-syndicated">--not-syndicated</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-not-time-lapse">--not-time-lapse</a>
</li>
@@ -4989,6 +5098,8 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-query-function">--query-function</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-regex">--regex</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-saved-to-library">--saved-to-library</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-screenshot">--screenshot</a>
</li>
@@ -4999,6 +5110,8 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-shared">--shared</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-slow-mo">--slow-mo</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-syndicated">--syndicated</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-time-lapse">--time-lapse</a>
</li>
@@ -5173,12 +5286,16 @@
<li><a href="cli.html#cmdoption-osxphotos-sync-not-portrait">--not-portrait</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-not-reference">--not-reference</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-not-saved-to-library">--not-saved-to-library</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-not-screenshot">--not-screenshot</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-not-selfie">--not-selfie</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-not-slow-mo">--not-slow-mo</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-not-syndicated">--not-syndicated</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-not-time-lapse">--not-time-lapse</a>
</li>
@@ -5201,6 +5318,8 @@
<li><a href="cli.html#cmdoption-osxphotos-sync-regex">--regex</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-R">--report</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-saved-to-library">--saved-to-library</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-screenshot">--screenshot</a>
</li>
@@ -5211,6 +5330,8 @@
<li><a href="cli.html#cmdoption-osxphotos-sync-s">--set</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-slow-mo">--slow-mo</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-syndicated">--syndicated</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-sync-theme">--theme</a>
</li>
@@ -5612,6 +5733,12 @@
<h2>S</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.PhotoInfo.saved_to_library">saved_to_library (osxphotos.PhotoInfo property)</a>
<ul>
<li><a href="reference.html#osxphotos.QueryOptions.saved_to_library">(osxphotos.QueryOptions attribute)</a>
</li>
</ul></li>
<li><a href="reference.html#osxphotos.PhotoInfo.score">score (osxphotos.PhotoInfo property)</a>
</li>
<li><a href="reference.html#osxphotos.ScoreInfo">ScoreInfo (class in osxphotos)</a>
@@ -5693,6 +5820,12 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-help-arg-SUBTOPIC">osxphotos-help command line option</a>
</li>
</ul></li>
<li><a href="reference.html#osxphotos.PhotoInfo.syndicated">syndicated (osxphotos.PhotoInfo property)</a>
<ul>
<li><a href="reference.html#osxphotos.QueryOptions.syndicated">(osxphotos.QueryOptions attribute)</a>
</li>
</ul></li>
</ul></td>
@@ -5703,6 +5836,8 @@
<h2>T</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.PhotoInfo.tables">tables() (osxphotos.PhotoInfo method)</a>
</li>
<li><a href="reference.html#osxphotos.SearchInfo.text_found">text_found (osxphotos.SearchInfo property)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoInfo.time_lapse">time_lapse (osxphotos.PhotoInfo property)</a>

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos" href="overview.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>osxphotos 0.59.0 documentation</title>
<title>osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="#"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="#"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="#">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -517,6 +517,7 @@
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.project_info"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.project_info</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.raw_original"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.raw_original</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.render_template"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.render_template()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.saved_to_library"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.saved_to_library</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.score"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.score</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.screenshot"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.screenshot</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.search_info"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.search_info</span></code></a></li>
@@ -524,6 +525,8 @@
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.selfie"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.selfie</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.shared"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.shared</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.slow_mo"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.slow_mo</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.syndicated"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.syndicated</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.tables"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.tables()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.time_lapse"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.time_lapse</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.title"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.title</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.PhotoInfo.tzoffset"><code class="docutils literal notranslate"><span class="pre">PhotoInfo.tzoffset</span></code></a></li>
@@ -590,7 +593,11 @@
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="reference.html#osxphotos.PlaceInfo"><code class="docutils literal notranslate"><span class="pre">PlaceInfo</span></code></a></li>
<li class="toctree-l2"><a class="reference internal" href="reference.html#osxphotos.ProjectInfo"><code class="docutils literal notranslate"><span class="pre">ProjectInfo</span></code></a></li>
<li class="toctree-l2"><a class="reference internal" href="reference.html#osxphotos.ProjectInfo"><code class="docutils literal notranslate"><span class="pre">ProjectInfo</span></code></a><ul>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.ProjectInfo.folder_list"><code class="docutils literal notranslate"><span class="pre">ProjectInfo.folder_list</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.ProjectInfo.folder_names"><code class="docutils literal notranslate"><span class="pre">ProjectInfo.folder_names</span></code></a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="reference.html#osxphotos.QueryOptions"><code class="docutils literal notranslate"><span class="pre">QueryOptions</span></code></a><ul>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.QueryOptions.added_after"><code class="docutils literal notranslate"><span class="pre">QueryOptions.added_after</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.QueryOptions.added_before"><code class="docutils literal notranslate"><span class="pre">QueryOptions.added_before</span></code></a></li>
@@ -672,6 +679,10 @@
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.QueryOptions.uti"><code class="docutils literal notranslate"><span class="pre">QueryOptions.uti</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.QueryOptions.uuid"><code class="docutils literal notranslate"><span class="pre">QueryOptions.uuid</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.QueryOptions.year"><code class="docutils literal notranslate"><span class="pre">QueryOptions.year</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.QueryOptions.syndicated"><code class="docutils literal notranslate"><span class="pre">QueryOptions.syndicated</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.QueryOptions.not_syndicated"><code class="docutils literal notranslate"><span class="pre">QueryOptions.not_syndicated</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.QueryOptions.saved_to_library"><code class="docutils literal notranslate"><span class="pre">QueryOptions.saved_to_library</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#osxphotos.QueryOptions.not_saved_to_library"><code class="docutils literal notranslate"><span class="pre">QueryOptions.not_saved_to_library</span></code></a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="reference.html#osxphotos.ScoreInfo"><code class="docutils literal notranslate"><span class="pre">ScoreInfo</span></code></a><ul>

Binary file not shown.

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Python Package Overview" href="package_overview.html" /><link rel="prev" title="OSXPhotos Command Line Interface (CLI)" href="cli.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>OSXPhotos Template System - osxphotos 0.59.0 documentation</title>
<title>OSXPhotos Template System - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -613,7 +613,7 @@
</td>
</tr>
<tr class="row-odd"><td><p>{osxphotos_version}</p></td>
<td><p>The osxphotos version, e.g. 0.59.0</p></td>
<td><p>The osxphotos version, e.g. 0.60.5</p></td>
</tr>
<tr class="row-even"><td><p>{osxphotos_cmd_line}</p></td>
<td><p>The full command line used to run osxphotos</p></td>

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Command Line Interface (CLI)" href="cli.html" /><link rel="prev" title="OSXPhotos" href="overview.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
<title>OSXPhotos Tutorial - osxphotos 0.59.0 documentation</title>
<title>OSXPhotos Tutorial - osxphotos 0.60.5 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.59.0 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.60.5 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.60.5 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">

View File

@@ -361,7 +361,7 @@ Template Substitutions
* - {tab}
- :A tab: '\t'
* - {osxphotos_version}
- The osxphotos version, e.g. '0.59.0'
- The osxphotos version, e.g. '0.60.5'
* - {osxphotos_cmd_line}
- The full command line used to run osxphotos
* - {album}

View File

@@ -21,7 +21,6 @@ from osxphotos.sqlitekvstore import SQLiteKVStore
class Latitude(click.ParamType):
name = "Latitude"
def convert(self, value, param, ctx):
@@ -37,7 +36,6 @@ class Latitude(click.ParamType):
class Longitude(click.ParamType):
name = "Longitude"
def convert(self, value, param, ctx):

View File

@@ -23,10 +23,7 @@ to your function. You can then do whatever you want with the photos.
from __future__ import annotations
import osxphotos
from osxphotos.cli import (
selection_command,
verbose,
)
from osxphotos.cli import selection_command, verbose
@selection_command

View File

@@ -2,9 +2,10 @@
import sys
import osxphotos
import click
import osxphotos
@click.command()
@click.argument("album1")

View File

@@ -1,6 +1,7 @@
import osxphotos
import os.path
import osxphotos
def main():
db = osxphotos.utils.get_system_library_path()

View File

@@ -32,7 +32,7 @@ import osxphotos
default=False,
)
def export(export_path, default_album, library_path, edited):
""" Export all photos, organized by album """
"""Export all photos, organized by album"""
export_path = os.path.expanduser(export_path)
library_path = os.path.expanduser(library_path) if library_path else None
@@ -66,11 +66,11 @@ def export(export_path, default_album, library_path, edited):
os.makedirs(dest_dir)
filename = p.original_filename
# export the photo but only if --edited, photo has adjustments, and
# export the photo but only if --edited, photo has adjustments, and
# path_edited is not None (can be None if edited photo is missing)
if edited and p.hasadjustments and p.path_edited:
# export edited version
# use original filename with _edited appended but make sure suffix is
# use original filename with _edited appended but make sure suffix is
# same as edited file
edited_filename = f"{pathlib.Path(filename).stem}_edited{pathlib.Path(p.path_edited).suffix}"
exported = p.export(dest_dir, edited_filename, edited=True)

View File

@@ -32,7 +32,7 @@ import osxphotos
default=None,
)
def export(export_path, library_path, uuid):
""" export photos to export_path and draw faces """
"""export photos to export_path and draw faces"""
library_path = os.path.expanduser(library_path) if library_path else None
if library_path is not None:
photosdb = osxphotos.PhotosDB(library_path)
@@ -61,7 +61,7 @@ def export(export_path, library_path, uuid):
def get_circle_points(xy, radius):
""" Returns tuples of (x0, y0), (x1, y1) for a circle centered at x, y with radius
"""Returns tuples of (x0, y0), (x1, y1) for a circle centered at x, y with radius
Arguments:
xy: tuple of x, y coordinates

126
examples/export_previews.py Normal file
View File

@@ -0,0 +1,126 @@
""" Export previews for photos in the Photos library
To run this with osxphotos on currently selected photos:
osxphotos run export_previews.py --selected export_path
"""
from __future__ import annotations
import pathlib
import shutil
import click
import osxphotos
from osxphotos.cli import echo, echo_error, query_command, verbose
from osxphotos.cli.export import get_dirnames_from_template, get_filenames_from_template
from osxphotos.cli.param_types import TemplateString
from osxphotos.utils import pluralize
@query_command
@click.option("--dry-run", is_flag=True, help="Dry run, don't actually export")
@click.option(
"--directory",
type=TemplateString(),
help="Directory to export to under export_path. This is an osxphotos template string. "
"For example, to export previews to directories based on creation date: "
"--directory '{created.year}/{created.mm}/{created.dd}' ",
)
@click.option(
"--filename",
"filename_template",
type=TemplateString(),
help="Filename for exported preview. "
"This is an osxphotos template string; "
"for example, to export previews to filename based on creation date: "
"--filename '{original_name}-{created.year}-{created.mm}-{created.dd}' ",
)
@click.argument(
"export_dir", type=click.Path(exists=True, file_okay=False, dir_okay=True)
)
def export_previews(
photos: list[osxphotos.PhotoInfo],
dry_run: bool,
directory: str,
filename_template: str,
export_dir: str,
**kwargs,
):
"""Export previews for photos in the Photos library.
Pass one or more query options to select photos to export or use without query options
to export all images.
If --directory is passed, the previews will be exported to subdirectories under export_path
based on the template string passed to --directory. For example, to export previews to
directories based on creation date: --directory '{created.year}/{created.mm}/{created.dd}'
If --filename is passed, the previews will be exported with the filename specified by the
template string passed to --filename. For example, to export previews to filename based on
creation date: --filename '{original_name}-{created.year}-{created.mm}-{created.dd}'
If --dry-run is passed, the previews will not actually be exported but the export will be
simulated and the export paths printed to stdout.
"""
verbose(f"Found {len(photos)} photo(s)")
count = 0
for photo in photos:
try:
# first derivative is the largest preview, so use that one
preview = pathlib.Path(photo.path_derivatives[0])
except IndexError:
echo(f"No preview for {photo.original_filename} ({photo.uuid})")
continue
verbose(f"Found preview for {photo.original_filename}: {preview}")
count += export_preview_to_directory_with_filename(
photo, preview, export_dir, directory, filename_template, dry_run
)
echo(f"Exported {count} preview {pluralize(count, 'image', 'images')}")
def export_preview_to_directory_with_filename(
photo: osxphotos.PhotoInfo,
preview_path: pathlib.Path,
export_dir: str,
directory: str,
filename_template: str,
dry_run: bool,
) -> int:
"""Export preview for photo to directory with filename; returns count of images exported"""
count = 0
for dirname in get_dirnames_from_template(
photo=photo,
directory=directory,
export_by_date=False,
dest=export_dir,
dry_run=dry_run,
):
for filename in get_filenames_from_template(
photo=photo,
filename_template=filename_template,
export_dir=export_dir,
dest_path=dirname,
original_name=True,
):
# need to change filename extension to match preview
filename = pathlib.Path(filename).with_suffix(preview_path.suffix)
dest_path = pathlib.Path(dirname) / filename
echo(
f"Exporting preview for {photo.original_filename} ({photo.uuid}) to {dest_path}"
)
if not dry_run:
try:
shutil.copy(preview_path, dest_path)
except Exception as e:
echo_error(f"Error exporting preview: {e}")
continue
count += 1
return count
if __name__ == "__main__":
export_previews()

View File

@@ -77,7 +77,9 @@ def place_folder(photo: osxphotos.PhotoInfo) -> str:
return ""
def photos_folders(photo: osxphotos.PhotoInfo, options: osxphotos.phototemplate.RenderOptions, **kwargs) -> Union[List, str]:
def photos_folders(
photo: osxphotos.PhotoInfo, options: osxphotos.phototemplate.RenderOptions, **kwargs
) -> Union[List, str]:
"""template function for use with --directory to export photos in a folder structure similar to Photos
Args:

View File

@@ -29,7 +29,9 @@ def main():
exported = photo.export(tempdir.name, use_photos_export=True, timeout=300)
if photo.hasadjustments:
exported.extend(
photo.export(tempdir.name, use_photos_export=True, edited=True, timeout=300)
photo.export(
tempdir.name, use_photos_export=True, edited=True, timeout=300
)
)
for filename in exported:
print(f"Removing temporary file {filename}")

View File

@@ -10,7 +10,7 @@ from osxphotos._constants import TIME_DELTA
@dataclass
class Comment:
""" Class for shared photo comments """
"""Class for shared photo comments"""
uuid: str
sort_fok: int
@@ -22,7 +22,7 @@ class Comment:
@dataclass
class Like:
""" Class for shared photo likes """
"""Class for shared photo likes"""
uuid: str
sort_fok: int
@@ -32,10 +32,10 @@ class Like:
def get_shared_person_info(photosdb, hashed_person_id):
""" returns tuple of (first name, last name, full name)
for person invited to shared album with
"""returns tuple of (first name, last name, full name)
for person invited to shared album with
ZINVITEEHASHEDPERSONID = hashed_person_id
Args:
photosdb: a osxphotos.PhotosDB object
hashed_person_id: str, value of ZINVITEEHASHEDPERSONID to lookup
@@ -66,12 +66,12 @@ def get_shared_person_info(photosdb, hashed_person_id):
def get_comments(photosdb, uuid):
""" return comments and likes, if any, for photo with uuid
"""return comments and likes, if any, for photo with uuid
Args:
photosdb: a osxphotos.PhotosDB object
uuid: uuid of the photo
Returns:
tuple of (list of comments as Comment objects or [] if no comments, list of likes as Like objects or [] if no likes)
"""

View File

@@ -15,14 +15,14 @@ import time
import click
import osxphotos
from osxphotos.cli import get_photos_db, _list_libraries
from osxphotos.cli import _list_libraries, get_photos_db
def show(photo):
""" open image with default image viewer
Note: This is for debugging only -- it will actually open any filetype which could
be very, very bad.
"""open image with default image viewer
Note: This is for debugging only -- it will actually open any filetype which could
be very, very bad.
Args:
photo: PhotoInfo object or a path to a photo on disk

View File

@@ -1,8 +1,10 @@
""" Example function for use with osxphotos import --post-function option """
import typing as t
import photoscript
import pathlib
import typing as t
import photoscript
from osxphotos.cli.import_cli import ReportRecord

View File

@@ -3,6 +3,7 @@ import os.path
import osxphotos
def main():
db = os.path.expanduser("~/Pictures/Photos Library.photoslibrary")
photosdb = osxphotos.PhotosDB(db)
@@ -15,11 +16,14 @@ def main():
print(photosdb.albums_as_dict)
# find all photos with Keyword = Foo and containing John Smith
photos = photosdb.photos(keywords=["Foo"],persons=["John Smith"])
photos = photosdb.photos(keywords=["Foo"], persons=["John Smith"])
# find all photos that include Alice Smith but do not contain the keyword Bar
photos = [p for p in photosdb.photos(persons=["Alice Smith"])
if p not in photosdb.photos(keywords=["Bar"]) ]
photos = [
p
for p in photosdb.photos(persons=["Alice Smith"])
if p not in photosdb.photos(keywords=["Bar"])
]
for p in photos:
print(
p.uuid,
@@ -34,5 +38,6 @@ def main():
p.path,
)
if __name__ == "__main__":
main()
main()

View File

@@ -10,8 +10,8 @@
from typing import List
def myfilter(values: List[str]) -> List[str]:
""" Custom filter to append "foo-" to template value """
"""Custom filter to append "foo-" to template value"""
values = ["foo-" + val for val in values]
return values

View File

@@ -7,7 +7,7 @@ from typing import Callable
from osxphotos import ExportResults, PhotoInfo
from osxphotos.exiftool import ExifTool
from osxphotos.utils import normalize_unicode
from osxphotos.unicode import normalize_unicode
# Update this for your custom keyword to rating mapping
RATINGS = {

View File

@@ -16,15 +16,19 @@ from .momentinfo import MomentInfo
from .personinfo import PersonInfo
from .photoexporter import ExportOptions, ExportResults, PhotoExporter
from .photoinfo import PhotoInfo
from .photosalbum import PhotosAlbum, PhotosAlbumPhotoScript
from .photosdb import PhotosDB
from .photosdb._photosdb_process_comments import CommentInfo, LikeInfo
from .phototables import PhotoTables
from .phototemplate import PhotoTemplate
from .placeinfo import PlaceInfo
from .platform import is_macos
from .queryoptions import QueryOptions
from .scoreinfo import ScoreInfo
from .searchinfo import SearchInfo
if is_macos:
from .photosalbum import PhotosAlbum, PhotosAlbumPhotoScript
# configure logging; every module in osxphotos should use this logger
logging.basicConfig(
level=logging.DEBUG,
@@ -53,6 +57,7 @@ __all__ = [
"PersonInfo",
"PhotoExporter",
"PhotoInfo",
"PhotoTables",
"PhotoTemplate",
"PhotosAlbum",
"PhotosAlbumPhotoScript",

View File

@@ -18,9 +18,6 @@ OSXPHOTOS_URL = "https://github.com/RhetTbull/osxphotos"
# Apple Epoch is Jan 1, 2001
TIME_DELTA = (datetime(2001, 1, 1, 0, 0) - datetime(1970, 1, 1, 0, 0)).total_seconds()
# Unicode format to use for comparing strings
UNICODE_FORMAT = "NFC"
# which Photos library database versions have been tested
# Photos 2.0 (10.12.6) == 2622
# Photos 3.0 (10.13.6) == 3301
@@ -128,6 +125,8 @@ _TESTED_OS_VERSIONS = [
("13", "0"),
("13", "1"),
("13", "2"),
("13", "3"),
("13", "4"),
]
# Photos 5 has persons who are empty string if unidentified face

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.59.0"
__version__ = "0.60.5"

View File

@@ -50,9 +50,8 @@ def sort_list_by_keys(values, sort_keys):
ValueError: raised if len(values) != len(sort_keys)
"""
if len(values) != len(sort_keys):
return ValueError("values and sort_keys must have same length")
return list(zip(*sorted(zip(sort_keys, values))))[1]
raise ValueError("values and sort_keys must be same length")
return [x for _, x in sorted(zip(sort_keys, values))]
class AlbumInfoBaseClass:
@@ -166,14 +165,13 @@ class AlbumInfoBaseClass:
return self._owner
def asdict(self):
"""Return album info as a dict"""
"""Return album info as a dict; does not include photos"""
return {
"uuid": self.uuid,
"creation_date": self.creation_date,
"start_date": self.start_date,
"end_date": self.end_date,
"owner": self.owner,
"photos": [p.uuid for p in self.photos],
}
def __len__(self):
@@ -299,7 +297,7 @@ class AlbumInfo(AlbumInfoBaseClass):
)
def asdict(self):
"""Return album info as a dict"""
"""Return album info as a dict; does not include photos"""
dict_data = super().asdict()
dict_data["title"] = self.title
dict_data["folder_names"] = self.folder_names
@@ -362,14 +360,13 @@ class ImportInfo(AlbumInfoBaseClass):
return self._photos
def asdict(self):
"""Return import info as a dict"""
"""Return import info as a dict; does not include photos"""
return {
"uuid": self.uuid,
"creation_date": self.creation_date,
"start_date": self.start_date,
"end_date": self.end_date,
"title": self.title,
"photos": [p.uuid for p in self.photos],
}
def __bool__(self):
@@ -386,7 +383,25 @@ class ProjectInfo(AlbumInfo):
Projects are cards, calendars, slideshows, etc.
"""
...
@property
def folder_names(self):
"""Return hierarchical list of folders the album is contained in
the folder list is in form:
["Top level folder", "sub folder 1", "sub folder 2", ...]
or empty list if album is not in any folders
"""
# projects are not in folders
return []
@property
def folder_list(self):
"""Returns list of FolderInfo objects for each folder the album is contained in
or empty list if album is not in any folders
"""
# projects are not in folders
return []
class FolderInfo:

View File

@@ -13,6 +13,7 @@ from osxphotos.debug import (
set_debug,
wrap_function,
)
from osxphotos.platform import is_macos
# apply any debug functions
# need to do this before importing anything else so that the debug functions
@@ -44,9 +45,7 @@ if args.get("--debug", False):
print("Debugging enabled", file=sys.stderr)
from .about import about
from .add_locations import add_locations
from .albums import albums
from .batch_edit import batch_edit
from .cli import cli_main
from .cli_commands import (
abort,
@@ -67,7 +66,6 @@ from .export import export
from .exportdb import exportdb
from .grep import grep
from .help import help
from .import_cli import import_cli
from .info import info
from .install_uninstall_run import install, run, uninstall
from .keywords import keywords
@@ -76,19 +74,24 @@ from .labels import labels
from .list import _list_libraries, list_libraries
from .orphans import orphans
from .persons import persons
from .photo_inspect import photo_inspect
from .places import places
from .query import query
from .repl import repl
from .show_command import show
from .snap_diff import diff, snap
from .sync import sync
from .theme import theme
from .timewarp import timewarp
from .tutorial import tutorial
from .uuid import uuid
from .version import version
if is_macos:
from .add_locations import add_locations
from .batch_edit import batch_edit
from .import_cli import import_cli
from .photo_inspect import photo_inspect
from .show_command import show
from .sync import sync
from .timewarp import timewarp
from .uuid import uuid
install_traceback()
__all__ = [

View File

@@ -5,9 +5,9 @@ from __future__ import annotations
import datetime
import click
import photoscript
import osxphotos
from osxphotos.platform import assert_macos
from osxphotos.queryoptions import IncompatibleQueryOptions, query_options_from_kwargs
from osxphotos.utils import pluralize
@@ -18,6 +18,10 @@ from .param_types import TimeOffset
from .rich_progress import rich_progress
from .verbose import get_verbose_console, verbose_print
assert_macos()
import photoscript
def get_location(
photos: list[osxphotos.PhotoInfo], idx: int, window: datetime.timedelta

View File

@@ -9,12 +9,16 @@ import json
import sys
import click
import photoscript
import osxphotos
from osxphotos.phototemplate import RenderOptions
from osxphotos.platform import assert_macos
from osxphotos.sqlitekvstore import SQLiteKVStore
assert_macos()
import photoscript
from .cli_commands import echo, echo_error, selection_command, verbose
from .kvstore import kvstore
from .param_types import Latitude, Longitude, TemplateString

View File

@@ -9,11 +9,10 @@ import click
from osxphotos._constants import PROFILE_SORT_KEYS
from osxphotos._version import __version__
from osxphotos.platform import is_macos
from .about import about
from .add_locations import add_locations
from .albums import albums
from .batch_edit import batch_edit
from .cli_params import DB_OPTION, DEBUG_OPTIONS, JSON_OPTION, VERSION_OPTION
from .common import OSXPHOTOS_HIDDEN
from .debug_dump import debug_dump
@@ -24,7 +23,6 @@ from .export import export
from .exportdb import exportdb
from .grep import grep
from .help import help
from .import_cli import import_cli
from .info import info
from .install_uninstall_run import install, run, uninstall
from .keywords import keywords
@@ -32,19 +30,24 @@ from .labels import labels
from .list import list_libraries
from .orphans import orphans
from .persons import persons
from .photo_inspect import photo_inspect
from .places import places
from .query import query
from .repl import repl
from .show_command import show
from .snap_diff import diff, snap
from .sync import sync
from .theme import theme
from .timewarp import timewarp
from .tutorial import tutorial
from .uuid import uuid
from .version import version
if is_macos:
from .add_locations import add_locations
from .batch_edit import batch_edit
from .import_cli import import_cli
from .photo_inspect import photo_inspect
from .show_command import show
from .sync import sync
from .timewarp import timewarp
from .uuid import uuid
# Click CLI object & context settings
class CLI_Obj:
@@ -106,11 +109,9 @@ def cli_main(ctx, db, json_, profile, profile_sort, **kwargs):
# install CLI commands
for command in [
commands = [
about,
add_locations,
albums,
batch_edit,
debug_dump,
diff,
docs_command,
@@ -120,7 +121,6 @@ for command in [
exportdb,
grep,
help,
import_cli,
info,
install,
keywords,
@@ -128,19 +128,28 @@ for command in [
list_libraries,
orphans,
persons,
photo_inspect,
places,
query,
repl,
run,
show,
snap,
sync,
theme,
timewarp,
tutorial,
uninstall,
uuid,
version,
]:
]
if is_macos:
commands += [
add_locations,
batch_edit,
import_cli,
photo_inspect,
show,
sync,
timewarp,
uuid,
]
for command in commands:
cli_main.add_command(command)

View File

@@ -1,12 +1,16 @@
"""Common options & parameters for osxphotos CLI commands"""
from __future__ import annotations
import contextlib
import functools
from textwrap import dedent
from typing import Any, Callable
import click
from ..platform import is_macos
from .common import OSXPHOTOS_HIDDEN, print_version
from .param_types import *
@@ -25,6 +29,49 @@ __all__ = [
]
def validate_selected(ctx, param, value):
""" "Validate photos are actually selected when --selected is used"""
if not value:
# --selected not used, just return
return value
# imports here to avoid conflict with linux port
# TODO: fix this once linux port is complete
import photoscript
from applescript import ScriptError
selection = None
with contextlib.suppress(ScriptError):
# ScriptError raised if selection made in edit mode or Smart Albums (on older versions of Photos)
selection = photoscript.PhotosLibrary().selection
if not selection:
click.echo(
dedent(
"""
--selected option used but no photos selected in Photos.
To select photos in Photos use one of the following methods:
- Select a single photo: Click the photo, or press the arrow keys to quickly navigate to and select the photo.
- Select a group of adjacent photos in a day: Click the first photo, then hold down the Shift key while you click the last photo.
You can also hold down Shift and press the arrow keys, or simply drag to enclose the photos within the selection rectangle.
- Select photos in a day that are not adjacent to each other: Hold down the Command key as you click each photo.
- Deselect specific photos: Hold down the Command key and click the photos you want to deselect.
- Deselect all photos: Click an empty space in the window (not a photo).
"""
),
err=True,
)
ctx.exit(1)
return value
def _param_memo(f: Callable[..., Any], param: click.Parameter) -> None:
"""Add param to the list of params for a click.Command
This is directly from the click source code and
@@ -544,6 +591,26 @@ _QUERY_PARAMETERS_DICT = {
is_flag=True,
help="Search for photos that are not in iCloud (have not been synched)",
),
"--syndicated": click.Option(
["--syndicated"],
is_flag=True,
help="Search for photos that have been shared via syndication ('Shared with You' album via Messages, etc.)",
),
"--not-syndicated": click.Option(
["--not-syndicated"],
is_flag=True,
help="Search for photos that have not been shared via syndication ('Shared with You' album via Messages, etc.)",
),
"--saved-to-library": click.Option(
["--saved-to-library"],
is_flag=True,
help="Search for syndicated photos that have saved to the library",
),
"--not-saved-to-library": click.Option(
["--not-saved-to-library"],
is_flag=True,
help="Search for syndicated photos that have not saved to the library",
),
"--regex": click.Option(
["--regex"],
metavar="REGEX TEMPLATE",
@@ -557,6 +624,7 @@ _QUERY_PARAMETERS_DICT = {
["--selected"],
is_flag=True,
help="Filter for photos that are currently selected in Photos.",
callback=validate_selected,
),
"--exif": click.Option(
["--exif"],
@@ -597,6 +665,9 @@ _QUERY_PARAMETERS_DICT = {
),
}
if not is_macos:
del _QUERY_PARAMETERS_DICT["--selected"]
def QUERY_OPTIONS(
wrapped=None, *, exclude: list[str] | None = None

View File

@@ -119,7 +119,9 @@ def rich_echo(
# if not outputting to terminal, use a huge width to avoid wrapping
# otherwise tests fail
width = 10_000
console = get_rich_console() or Console(theme=theme or get_rich_theme(), width=width)
console = get_rich_console() or Console(
theme=theme or get_rich_theme(), width=width
)
if markdown:
message = Markdown(message)
# Markdown always adds a new line so disable unless explicitly specified

View File

@@ -15,7 +15,8 @@ from xdg import xdg_config_home, xdg_data_home
import osxphotos
from osxphotos._constants import APP_NAME
from osxphotos._version import __version__
from osxphotos.utils import get_latest_version, get_macos_version
from osxphotos.platform import get_macos_version
from osxphotos.utils import get_latest_version
# used to show/hide hidden commands
OSXPHOTOS_HIDDEN = not bool(os.getenv("OSXPHOTOS_SHOW_HIDDEN", default=False))

View File

@@ -1,19 +1,30 @@
"""Detect dark mode on MacOS >= 10.14"""
"""Detect dark mode on MacOS >= 10.14 or fake it elsewhere"""
import objc
import Foundation
from osxphotos.platform import is_macos
if is_macos:
import Foundation
import objc
def theme():
with objc.autorelease_pool():
user_defaults = Foundation.NSUserDefaults.standardUserDefaults()
system_theme = user_defaults.stringForKey_("AppleInterfaceStyle")
return "dark" if system_theme == "Dark" else "light"
def theme():
with objc.autorelease_pool():
user_defaults = Foundation.NSUserDefaults.standardUserDefaults()
system_theme = user_defaults.stringForKey_("AppleInterfaceStyle")
return "dark" if system_theme == "Dark" else "light"
def is_dark_mode():
return theme() == "dark"
def is_dark_mode():
return theme() == "dark"
def is_light_mode():
return theme() == "light"
else:
def is_light_mode():
return theme() == "light"
def theme():
return "light"
def is_dark_mode():
return theme() == "dark"
def is_light_mode():
return theme() == "light"

View File

@@ -57,8 +57,8 @@ def dump(
print_template,
):
"""Print list of all photos & associated info from the Photos library.
NOTE: dump is DEPRECATED and will be removed in a future release.
NOTE: dump is DEPRECATED and will be removed in a future release.
Use `osxphotos query` instead.
"""

View File

@@ -12,13 +12,6 @@ import time
from typing import Iterable, List, Optional, Tuple
import click
from osxmetadata import (
MDITEM_ATTRIBUTE_DATA,
MDITEM_ATTRIBUTE_SHORT_NAMES,
OSXMetaData,
Tag,
)
from osxmetadata.constants import _TAGS_NAMES
import osxphotos
from osxphotos._constants import (
@@ -47,25 +40,31 @@ from osxphotos.datetime_formatter import DateTimeFormatter
from osxphotos.debug import is_debug
from osxphotos.exiftool import get_exiftool_path
from osxphotos.export_db import ExportDB, ExportDBInMemory
from osxphotos.fileutil import FileUtil, FileUtilNoOp, FileUtilShUtil
from osxphotos.fileutil import FileUtilMacOS, FileUtilNoOp, FileUtilShUtil
from osxphotos.path_utils import is_valid_filepath, sanitize_filename, sanitize_filepath
from osxphotos.photoexporter import ExportOptions, ExportResults, PhotoExporter
from osxphotos.photoinfo import PhotoInfoNone
from osxphotos.photokit import (
check_photokit_authorization,
request_photokit_authorization,
)
from osxphotos.photosalbum import PhotosAlbum
from osxphotos.phototemplate import PhotoTemplate, RenderOptions
from osxphotos.platform import get_macos_version, is_macos
from osxphotos.queryoptions import load_uuid_from_file, query_options_from_kwargs
from osxphotos.unicode import normalize_fs_path
from osxphotos.uti import get_preferred_uti_extension
from osxphotos.utils import (
format_sec_to_hhmmss,
get_macos_version,
normalize_fs_path,
pluralize,
under_test,
)
from osxphotos.utils import format_sec_to_hhmmss, pluralize, under_test
if is_macos:
from osxmetadata import (
MDITEM_ATTRIBUTE_DATA,
MDITEM_ATTRIBUTE_SHORT_NAMES,
OSXMetaData,
Tag,
)
from osxmetadata.constants import _TAGS_NAMES
from osxphotos.photokit import (
check_photokit_authorization,
request_photokit_authorization,
)
from osxphotos.photosalbum import PhotosAlbum
from .cli_commands import logger
from .cli_params import (
@@ -851,7 +850,6 @@ def export(
retry,
save_config,
screenshot,
selected,
selfie,
shared,
sidecar,
@@ -883,6 +881,11 @@ def export(
verbose_flag,
xattr_template,
year,
syndicated,
not_syndicated,
saved_to_library,
not_saved_to_library,
selected=False, # Isn't provided on unsupported platforms
# debug, # debug, watch, breakpoint handled in cli/__init__.py
# watch,
# breakpoint,
@@ -911,7 +914,8 @@ def export(
# capture locals for use with ConfigOptions before changing any of them
locals_ = locals()
set_crash_data("locals", locals_)
crash_data = locals_.copy()
set_crash_data("locals", crash_data)
# config expects --verbose to be named "verbose" not "verbose_flag"
locals_["verbose"] = verbose_flag
@@ -1102,6 +1106,11 @@ def export(
verbose_flag = cfg.verbose
xattr_template = cfg.xattr_template
year = cfg.year
syndicated = cfg.syndicated
not_syndicated = cfg.not_syndicated
saved_to_library = cfg.saved_to_library
not_saved_to_library = cfg.not_saved_to_library
# config file might have changed verbose
verbose = verbose_print(verbose=verbose_flag, timestamp=timestamp, theme=theme)
verbose(f"Loaded options from file [filepath]{load_config}")
@@ -1110,7 +1119,10 @@ def export(
verbose(f"osxphotos version: {__version__}")
verbose(f"Python version: {sys.version}")
verbose(f"Platform: {platform.platform()}, {'.'.join(get_macos_version())}")
if is_macos:
verbose(f"Platform: {platform.platform()}, {'.'.join(get_macos_version())}")
else:
verbose(f"Platform: {platform.platform()}")
verbose(f"Verbose level: {verbose_flag}")
# validate options
@@ -1147,6 +1159,8 @@ def export(
("slow_mo", "not_slow_mo"),
("time_lapse", "not_time_lapse"),
("title", "no_title"),
("syndicated", "not_syndicated"),
("saved_to_library", "not_saved_to_library"),
]
dependent_options = [
("append", ("report")),
@@ -1324,7 +1338,7 @@ def export(
if ramdb
else ExportDB(dbfile=export_db_path, export_dir=dest)
)
fileutil = FileUtilShUtil if alt_copy else FileUtil
fileutil = FileUtilShUtil if alt_copy or not is_macos else FileUtilMacOS
if verbose:
if export_db.was_created:
@@ -1427,21 +1441,22 @@ def export(
photo_num = 0
num_exported = 0
limit_str = f" (limit = [num]{limit}[/num])" if limit else ""
# hack to avoid passing all the options to export_photo
kwargs = {
k: v
for k, v in locals().items()
if k in inspect.getfullargspec(export_photo).args
}
kwargs["export_dir"] = dest
kwargs["export_preview"] = preview
with rich_progress(console=get_verbose_console(), mock=no_progress) as progress:
task = progress.add_task(
f"Exporting [num]{num_photos}[/] photos{limit_str}", total=num_photos
)
for p in photos:
photo_num += 1
# hack to avoid passing all the options to export_photo
kwargs = {
k: v
for k, v in locals().items()
if k in inspect.getfullargspec(export_photo).args
}
kwargs["photo"] = p
kwargs["export_dir"] = dest
kwargs["export_preview"] = preview
kwargs["photo_num"] = photo_num
export_results = export_photo(**kwargs)
if post_function:
for function in post_function:
@@ -1711,7 +1726,7 @@ def export_photo(
keyword_template=None,
description_template=None,
export_db=None,
fileutil=FileUtil,
fileutil=FileUtilShUtil,
dry_run=None,
touch_file=None,
edited_suffix="_edited",
@@ -2253,7 +2268,7 @@ def export_photo_to_directory(
err=True,
)
if tries > retry:
results.error.append((str(pathlib.Path(dest) / filename), e))
results.error.append((str(pathlib.Path(dest) / filename), str(e)))
break
else:
rich_echo(

View File

@@ -19,14 +19,14 @@ from osxphotos.export_db_utils import (
export_db_backup,
export_db_check_signatures,
export_db_get_errors,
export_db_get_last_library,
export_db_get_last_run,
export_db_get_version,
export_db_migrate_photos_library,
export_db_save_config_to_file,
export_db_touch_files,
export_db_update_signatures,
export_db_vacuum,
export_db_migrate_photos_library,
export_db_get_last_library,
)
from osxphotos.utils import pluralize

View File

@@ -5,7 +5,6 @@ import re
import typing as t
import click
from osxmetadata import MDITEM_ATTRIBUTE_DATA, MDITEM_ATTRIBUTE_SHORT_NAMES
from rich.console import Console
from rich.markdown import Markdown
@@ -21,6 +20,10 @@ from osxphotos.phototemplate import (
TEMPLATE_SUBSTITUTIONS_PATHLIB,
get_template_help,
)
from osxphotos.platform import is_macos
if is_macos:
from osxmetadata import MDITEM_ATTRIBUTE_DATA, MDITEM_ATTRIBUTE_SHORT_NAMES
from .click_rich_echo import rich_echo_via_pager
from .color_themes import get_theme
@@ -249,68 +252,73 @@ class ExportCommand(click.Command):
+ f"rebuilding the '{OSXPHOTOS_EXPORT_DB}' database."
)
formatter.write("\n")
formatter.write(
rich_text("## Extended Attributes", width=formatter.width, markdown=True)
)
formatter.write("\n")
formatter.write_text(
"""
Some options (currently '--finder-tag-template', '--finder-tag-keywords', '-xattr-template') write
additional metadata accessible by Spotlight to facilitate searching.
For example, --finder-tag-keyword writes all keywords (including any specified by '--keyword-template'
or other options) to Finder tags that are searchable in Spotlight using the syntax: 'tag:tagname'.
For example, if you have images with keyword "Travel" then using '--finder-tag-keywords' you could quickly
find those images in the Finder by typing 'tag:Travel' in the Spotlight search bar.
Finder tags are written to the 'com.apple.metadata:_kMDItemUserTags' extended attribute.
Unlike EXIF metadata, extended attributes do not modify the actual file;
the metadata is written to extended attributes associated with the file and the Spotlight metadata database.
Most cloud storage services do not synch extended attributes.
Dropbox does sync them and any changes to a file's extended attributes
will cause Dropbox to re-sync the files.
The following attributes may be used with '--xattr-template':
"""
)
# build help text from all the attribute names
# passed to click.HelpFormatter.write_dl for formatting
attr_tuples = [
(
rich_text("[bold]Attribute[/bold]", width=formatter.width),
rich_text("[bold]Description[/bold]", width=formatter.width),
if is_macos:
formatter.write(
rich_text(
"## Extended Attributes", width=formatter.width, markdown=True
)
)
]
for attr_key in sorted(EXTENDED_ATTRIBUTE_NAMES):
# get short and long name
attr = MDITEM_ATTRIBUTE_SHORT_NAMES[attr_key]
short_name = MDITEM_ATTRIBUTE_DATA[attr]["short_name"]
long_name = MDITEM_ATTRIBUTE_DATA[attr]["name"]
constant = MDITEM_ATTRIBUTE_DATA[attr]["xattr_constant"]
formatter.write("\n")
formatter.write_text(
"""
Some options (currently '--finder-tag-template', '--finder-tag-keywords', '-xattr-template') write
additional metadata accessible by Spotlight to facilitate searching.
For example, --finder-tag-keyword writes all keywords (including any specified by '--keyword-template'
or other options) to Finder tags that are searchable in Spotlight using the syntax: 'tag:tagname'.
For example, if you have images with keyword "Travel" then using '--finder-tag-keywords' you could quickly
find those images in the Finder by typing 'tag:Travel' in the Spotlight search bar.
Finder tags are written to the 'com.apple.metadata:_kMDItemUserTags' extended attribute.
Unlike EXIF metadata, extended attributes do not modify the actual file;
the metadata is written to extended attributes associated with the file and the Spotlight metadata database.
Most cloud storage services do not synch extended attributes.
Dropbox does sync them and any changes to a file's extended attributes
will cause Dropbox to re-sync the files.
# get help text
description = MDITEM_ATTRIBUTE_DATA[attr]["description"]
type_ = MDITEM_ATTRIBUTE_DATA[attr]["help_type"]
attr_help = f"{long_name}; {constant}; {description}; {type_}"
The following attributes may be used with '--xattr-template':
# add to list
attr_tuples.append((short_name, attr_help))
"""
)
formatter.write_dl(attr_tuples)
formatter.write("\n")
formatter.write_text(
"For additional information on extended attributes see: https://developer.apple.com/documentation/coreservices/file_metadata/mditem/common_metadata_attribute_keys"
)
formatter.write("\n")
formatter.write(
rich_text("## Templating System", width=formatter.width, markdown=True)
)
formatter.write("\n")
help_text += formatter.getvalue()
help_text += template_help(width=formatter.width)
formatter = click.HelpFormatter(width=HELP_WIDTH)
# build help text from all the attribute names
# passed to click.HelpFormatter.write_dl for formatting
attr_tuples = [
(
rich_text("[bold]Attribute[/bold]", width=formatter.width),
rich_text("[bold]Description[/bold]", width=formatter.width),
)
]
for attr_key in sorted(EXTENDED_ATTRIBUTE_NAMES):
# get short and long name
attr = MDITEM_ATTRIBUTE_SHORT_NAMES[attr_key]
short_name = MDITEM_ATTRIBUTE_DATA[attr]["short_name"]
long_name = MDITEM_ATTRIBUTE_DATA[attr]["name"]
constant = MDITEM_ATTRIBUTE_DATA[attr]["xattr_constant"]
# get help text
description = MDITEM_ATTRIBUTE_DATA[attr]["description"]
type_ = MDITEM_ATTRIBUTE_DATA[attr]["help_type"]
attr_help = f"{long_name}; {constant}; {description}; {type_}"
# add to list
attr_tuples.append((short_name, attr_help))
formatter.write_dl(attr_tuples)
formatter.write("\n")
formatter.write_text(
"For additional information on extended attributes see: https://developer.apple.com/documentation/coreservices/file_metadata/mditem/common_metadata_attribute_keys"
)
formatter.write("\n")
formatter.write(
rich_text("## Templating System", width=formatter.width, markdown=True)
)
formatter.write("\n")
help_text += formatter.getvalue()
help_text += template_help(width=formatter.width)
formatter = click.HelpFormatter(width=HELP_WIDTH)
formatter.write("\n")
formatter.write("\n")
formatter.write_text(
"With the --directory and --filename options you may specify a template for the "
+ "export directory or filename, respectively. "

View File

@@ -20,7 +20,6 @@ from textwrap import dedent
from typing import Callable, Dict, List, Optional, Tuple, Union
import click
from photoscript import Photo, PhotosLibrary
from rich.console import Console
from rich.markdown import Markdown
from strpdatetime import strpdatetime
@@ -42,9 +41,15 @@ from osxphotos.exiftool import ExifToolCaching, get_exiftool_path
from osxphotos.photoinfo import PhotoInfoNone
from osxphotos.photosalbum import PhotosAlbumPhotoScript
from osxphotos.phototemplate import PhotoTemplate, RenderOptions
from osxphotos.platform import assert_macos
from osxphotos.sqlitekvstore import SQLiteKVStore
from osxphotos.unicode import normalize_unicode
from osxphotos.utils import pluralize
assert_macos()
from photoscript import Photo, PhotosLibrary
from .cli_params import THEME_OPTION
from .click_rich_echo import rich_click_echo, rich_echo_error
from .rich_progress import rich_progress
@@ -354,11 +359,12 @@ def set_photo_metadata(
merge_keywords: bool,
) -> MetaData:
"""Set metadata (title, description, keywords) for a Photo object"""
photo.title = metadata.title
photo.description = metadata.description
photo.title = normalize_unicode(metadata.title)
photo.description = normalize_unicode(metadata.description)
keywords = metadata.keywords.copy()
keywords =normalize_unicode(keywords)
if merge_keywords:
if old_keywords := photo.keywords:
if old_keywords := normalize_unicode(photo.keywords):
keywords.extend(old_keywords)
keywords = list(set(keywords))
photo.keywords = keywords
@@ -417,7 +423,7 @@ def set_photo_title(
verbose(
f"Setting title of photo [filename]{filepath.name}[/] to '{title_text[0]}'"
)
photo.title = title_text[0]
photo.title = normalize_unicode(title_text[0])
return title_text[0]
else:
return ""
@@ -445,7 +451,7 @@ def set_photo_description(
verbose(
f"Setting description of photo [filename]{filepath.name}[/] to '{description_text[0]}'"
)
photo.description = description_text[0]
photo.description = normalize_unicode(description_text[0])
return description_text[0]
else:
return ""
@@ -466,8 +472,9 @@ def set_photo_keywords(
kw = render_photo_template(filepath, relative_filepath, keyword, exiftool_path)
keywords.extend(kw)
if keywords:
keywords = normalize_unicode(keywords)
if merge:
if old_keywords := photo.keywords:
if old_keywords := normalize_unicode(photo.keywords):
keywords.extend(old_keywords)
keywords = list(set(keywords))
verbose(f"Setting keywords of photo [filename]{filepath.name}[/] to {keywords}")

View File

@@ -68,7 +68,6 @@ class PathOrStdin(click.Path):
class DateTimeISO8601(click.ParamType):
name = "DATETIME"
def convert(self, value, param, ctx):
@@ -82,7 +81,6 @@ class DateTimeISO8601(click.ParamType):
class BitMathSize(click.ParamType):
name = "BITMATH"
def convert(self, value, param, ctx):
@@ -102,7 +100,6 @@ class BitMathSize(click.ParamType):
class TimeISO8601(click.ParamType):
name = "TIME"
def convert(self, value, param, ctx):
@@ -141,7 +138,6 @@ class FunctionCall(click.ParamType):
class ExportDBType(click.ParamType):
name = "EXPORTDB"
def convert(self, value, param, ctx):
@@ -279,7 +275,6 @@ class StrpDateTimePattern(click.ParamType):
class Latitude(click.ParamType):
name = "Latitude"
def convert(self, value, param, ctx):
@@ -295,7 +290,6 @@ class Latitude(click.ParamType):
class Longitude(click.ParamType):
name = "Longitude"
def convert(self, value, param, ctx):

View File

@@ -13,8 +13,6 @@ from typing import Generator, List, Optional, Tuple
import bitmath
import click
from applescript import ScriptError
from photoscript import PhotosLibrary
from rich.console import Console
from rich.layout import Layout
from rich.live import Live
@@ -22,10 +20,17 @@ from rich.panel import Panel
from osxphotos import PhotoInfo, PhotosDB
from osxphotos._constants import _UNKNOWN_PERSON, search_category_factory
from osxphotos.platform import assert_macos
from osxphotos.rich_utils import add_rich_markup_tag
from osxphotos.text_detection import detect_text as detect_text_in_photo
from osxphotos.utils import dd_to_dms_str
assert_macos()
from applescript import ScriptError
from photoscript import PhotosLibrary
from osxphotos.text_detection import detect_text as detect_text_in_photo
from .cli_params import DB_OPTION, THEME_OPTION
from .color_themes import get_theme
from .common import get_photos_db

View File

@@ -1,5 +1,7 @@
"""query command for osxphotos CLI"""
import sys
import click
import osxphotos
@@ -9,10 +11,13 @@ from osxphotos.cli.click_rich_echo import (
set_rich_theme,
)
from osxphotos.debug import set_debug
from osxphotos.photosalbum import PhotosAlbum
from osxphotos.phototemplate import RenderOptions
from osxphotos.platform import assert_macos, is_macos
from osxphotos.queryoptions import query_options_from_kwargs
if is_macos:
from osxphotos.photosalbum import PhotosAlbum
from .cli_params import (
DB_ARGUMENT,
DB_OPTION,
@@ -20,6 +25,7 @@ from .cli_params import (
FIELD_OPTION,
JSON_OPTION,
QUERY_OPTIONS,
make_click_option_decorator,
)
from .color_themes import get_default_theme
from .common import CLI_COLOR_ERROR, CLI_COLOR_WARNING, OSXPHOTOS_HIDDEN, get_photos_db
@@ -27,20 +33,29 @@ from .list import _list_libraries
from .print_photo_info import print_photo_fields, print_photo_info
from .verbose import get_verbose_console
MACOS_OPTIONS = make_click_option_decorator(
*[
click.Option(
["--add-to-album"],
metavar="ALBUM",
help="Add all photos from query to album ALBUM in Photos. Album ALBUM will be created "
"if it doesn't exist. All photos in the query results will be added to this album. "
"This only works if the Photos library being queried is the last-opened (default) library in Photos. "
"This feature is currently experimental. I don't know how well it will work on large query sets.",
),
]
if is_macos
else []
)
@click.command()
@DB_OPTION
@JSON_OPTION
@click.option("--count", is_flag=True, help="Print count of photos matching query and exit.")
@QUERY_OPTIONS
@DELETED_OPTIONS
@click.option(
"--add-to-album",
metavar="ALBUM",
help="Add all photos from query to album ALBUM in Photos. Album ALBUM will be created "
"if it doesn't exist. All photos in the query results will be added to this album. "
"This only works if the Photos library being queried is the last-opened (default) library in Photos. "
"This feature is currently experimental. I don't know how well it will work on large query sets.",
)
@MACOS_OPTIONS
@click.option(
"--quiet",
is_flag=True,
@@ -68,10 +83,11 @@ def query(
db,
field,
json_,
count,
print_template,
quiet,
add_to_album,
photos_library,
add_to_album=False,
**kwargs,
):
"""Query the Photos database using 1 or more search options;
@@ -123,7 +139,13 @@ def query(
# below needed for to make CliRunner work for testing
cli_json = cli_obj.json if cli_obj is not None else None
if count:
click.echo(len(photos))
return
if add_to_album and photos:
assert_macos()
album_query = PhotosAlbum(add_to_album, verbose=None)
photo_len = len(photos)
photo_word = "photos" if photo_len > 1 else "photo"

View File

@@ -1,14 +1,18 @@
"""repl command for osxphotos CLI"""
from __future__ import annotations
import os
import os.path
import pathlib
import re
import shlex
import subprocess
import sys
import time
from functools import partial
from typing import List
import click
import photoscript
from rich import pretty, print
import osxphotos
@@ -16,6 +20,7 @@ from osxphotos._constants import _PHOTOS_4_VERSION
from osxphotos.cli.click_rich_echo import rich_echo_error as echo_error
from osxphotos.photoinfo import PhotoInfo
from osxphotos.photosdb import PhotosDB
from osxphotos.platform import assert_macos, is_macos
from osxphotos.pyrepl import embed_repl
from osxphotos.queryoptions import (
IncompatibleQueryOptions,
@@ -23,6 +28,10 @@ from osxphotos.queryoptions import (
query_options_from_kwargs,
)
if is_macos:
import photoscript
from applescript import ScriptError
from .cli_params import DB_ARGUMENT, DB_OPTION, DELETED_OPTIONS, QUERY_OPTIONS
from .common import get_photos_db
@@ -52,7 +61,9 @@ def repl(ctx, cli_obj, db, emacs, beta, **kwargs):
import logging
from objexplore import explore
from photoscript import Album, Photo, PhotosLibrary
if is_macos:
from photoscript import Album, Photo, PhotosLibrary
from rich import inspect as _inspect
from osxphotos import ExifTool, PhotoInfo, PhotosDB
@@ -92,6 +103,7 @@ def repl(ctx, cli_obj, db, emacs, beta, **kwargs):
get_photo = photosdb.get_photo
show = _show_photo
spotlight = _spotlight_photo
find = partial(_find_in_library, photosdb)
get_selected = _get_selected(photosdb)
try:
selected = get_selected()
@@ -121,28 +133,31 @@ def repl(ctx, cli_obj, db, emacs, beta, **kwargs):
)
print(f"\nThe following functions may be helpful:")
print(
f"- get_photo(uuid): return a PhotoInfo object for photo with uuid; e.g. get_photo('B13F4485-94E0-41CD-AF71-913095D62E31')"
"- get_photo(uuid): return a PhotoInfo object for photo with uuid; e.g. get_photo('B13F4485-94E0-41CD-AF71-913095D62E31')"
)
print(
f"- get_selected(); return list of PhotoInfo objects for photos selected in Photos"
"- 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])"
"- 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')"
"- 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("- 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)"
"- 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)"
"- explore(object): interactively explore an object with objexplore; e.g. explore(PhotoInfo)"
)
print(f"- q, quit, quit(), exit, exit(): exit this interactive shell\n")
print(
"- find(text): search for files matching text in Photos library; e.g. find('B13F4485')"
)
print("- q, quit, quit(), exit, exit(): exit this interactive shell\n")
embed_repl(
globals=globals(),
@@ -191,15 +206,23 @@ def _get_selected(photosdb):
"""get list of PhotoInfo objects for photos selected in Photos"""
def get_selected():
selected = photoscript.PhotosLibrary().selection
if not selected:
return []
return photosdb.photos(uuid=[p.uuid for p in selected])
assert_macos()
try:
selected = photoscript.PhotosLibrary().selection
except ScriptError as e:
# some photos (e.g. shared items) can't be selected and raise ScriptError:
# applescript.ScriptError: Photos got an error: Cant get media item id "34C26DFA-0CEA-4DB7-8FDA-B87789B3209D/L0/001". (-1728) app='Photos' range=16820-16873
# In this case, we can parse the UUID from the error (though this only works for a single selected item)
if match := re.match(r".*Cant get media item id \"(.*)\".*", str(e)):
uuid = match[1].split("/")[0]
return photosdb.photos(uuid=[uuid])
return photosdb.photos(uuid=[p.uuid for p in selected]) if selected else []
return get_selected
def _spotlight_photo(photo: PhotoInfo):
assert_macos()
photo_ = photoscript.Photo(photo.uuid)
photo_.spotlight()
@@ -217,3 +240,20 @@ def _query_photos(photosdb: PhotosDB, query_options: QueryOptions) -> List:
) from e
return photos
def _find_in_library(photosdb: PhotosDB, search_str: str) -> list[str]:
"""Find files in Photos library matching search_str using find command"""
# this is a quick and dirty way to find files in the Photos library
# e.g. those matching a UUID or a filename
library_path = photosdb.library_path
if not library_path:
raise ValueError("Could not find Photos library")
search_str = shlex.quote(search_str)
library_path = shlex.quote(library_path)
cmd = f"find {library_path} | grep {search_str}"
output = subprocess.check_output(cmd, shell=True, universal_newlines=True)
# Split the output into lines and return as a list
return output.strip().split("\n")

View File

@@ -182,7 +182,9 @@ class ExportReportWriterSQLite(ReportWriterABC):
with suppress(FileNotFoundError):
os.unlink(self.output_file)
self._conn = sqlite3.connect(self.output_file, check_same_thread=SQLITE_CHECK_SAME_THREAD)
self._conn = sqlite3.connect(
self.output_file, check_same_thread=SQLITE_CHECK_SAME_THREAD
)
self._create_tables()
self.report_id = self._generate_report_id()
@@ -534,7 +536,9 @@ class SyncReportWriterSQLite(ReportWriterABC):
with suppress(FileNotFoundError):
os.unlink(self.output_file)
self._conn = sqlite3.connect(self.output_file, check_same_thread=SQLITE_CHECK_SAME_THREAD)
self._conn = sqlite3.connect(
self.output_file, check_same_thread=SQLITE_CHECK_SAME_THREAD
)
self._create_tables()
self.report_id = self._generate_report_id()

View File

@@ -7,12 +7,16 @@ import click
from osxphotos._constants import UUID_PATTERN
from osxphotos.export_db_utils import get_uuid_for_filepath
from osxphotos.photosdb.photosdb_utils import get_photos_library_version
from osxphotos.platform import assert_macos
from osxphotos.utils import get_last_library_path
assert_macos()
from osxphotos.photoscript_utils import (
photoscript_object_from_name,
photoscript_object_from_uuid,
)
from osxphotos.photosdb.photosdb_utils import get_photos_library_version
from osxphotos.utils import get_last_library_path
from .cli_commands import echo, echo_error
from .cli_params import DB_OPTION

View File

@@ -9,13 +9,13 @@ import pathlib
from typing import Any, Callable, Literal
import click
import photoscript
from osxphotos import PhotoInfo, PhotosDB, __version__
from osxphotos.photoinfo import PhotoInfoNone
from osxphotos.photosalbum import PhotosAlbum
from osxphotos.photosdb.photosdb_utils import get_db_version
from osxphotos.phototemplate import PhotoTemplate, RenderOptions
from osxphotos.platform import assert_macos
from osxphotos.queryoptions import (
IncompatibleQueryOptions,
QueryOptions,
@@ -24,6 +24,10 @@ from osxphotos.queryoptions import (
from osxphotos.sqlitekvstore import SQLiteKVStore
from osxphotos.utils import pluralize
assert_macos()
import photoscript
from .cli_params import (
DB_OPTION,
QUERY_OPTIONS,
@@ -167,6 +171,7 @@ def get_photo_metadata(photos: list[PhotoInfo]) -> str:
photos_dict[k] = v
elif photos_dict[k] and v != photos_dict[k]:
photos_dict[k] = f"{photos_dict[k]} {v}"
# convert photos_dict to JSON string
# wouldn't it be nice if json encoder handled datetimes...
def default(o):

View File

@@ -7,7 +7,6 @@ from functools import partial
from textwrap import dedent
import click
from photoscript import PhotosLibrary
from rich.console import Console
from osxphotos._constants import APP_NAME
@@ -25,10 +24,16 @@ from osxphotos.photodates import (
update_photo_from_function,
update_photo_time_for_new_timezone,
)
from osxphotos.photosalbum import PhotosAlbumPhotoScript
from osxphotos.phototz import PhotoTimeZone, PhotoTimeZoneUpdater
from osxphotos.platform import assert_macos
from osxphotos.utils import noop, pluralize
assert_macos()
from photoscript import PhotosLibrary
from osxphotos.photosalbum import PhotosAlbumPhotoScript
from .cli_params import THEME_OPTION, TIMESTAMP_OPTION, VERBOSE_OPTION
from .click_rich_echo import rich_click_echo as echo
from .click_rich_echo import rich_echo_error as echo_error
@@ -164,7 +169,7 @@ See also `osxphotos help timewarp` for more information on the timewarp
command which can be used to change the time zone of photos after import.
"""
""" # noqa: E501
),
width=formatter.width,
markdown=True,

View File

@@ -1,6 +1,11 @@
"""uuid command for osxphotos CLI"""
import click
from osxphotos.platform import assert_macos
assert_macos()
import photoscript

View File

@@ -1,11 +1,14 @@
"""about command for osxphotos CLI"""
import click
import os
import click
import packaging
from osxphotos._constants import OSXPHOTOS_URL
from osxphotos._version import __version__
from .common import get_latest_version
import packaging
@click.command(name="version")

View File

@@ -5,12 +5,17 @@ from typing import Callable, List, Optional, Tuple
from osxphotos import PhotosDB
from osxphotos.exiftool import ExifTool
from photoscript import Photo
from .datetime_utils import datetime_naive_to_local, datetime_to_new_tz
from .platform import assert_macos
from .utils import noop
assert_macos()
from photoscript import Photo
from .exif_datetime_updater import get_exif_date_time_offset
from .phototz import PhotoTimeZone
from .utils import noop
ExifDiff = namedtuple(
"ExifDiff",

View File

@@ -50,7 +50,7 @@ class ConfigOptions:
try:
arg = args[attr]
# don't test 'not arg'; need to handle empty strings as valid values
if arg is None or arg == False:
if arg is None or arg is False:
if type(self._attrs[attr]) == tuple:
setattr(self, attr, ())
else:

View File

@@ -21,6 +21,7 @@ __all__ = [
"utc_offset_seconds",
]
# TODO: look at https://github.com/regebro/tzlocal for more robust implementation
def get_local_tz(dt: datetime.datetime) -> datetime.tzinfo:
"""Return local timezone as datetime.timezone tzinfo for dt
@@ -207,4 +208,3 @@ def utc_offset_seconds(dt: datetime.datetime) -> int:
return dt.tzinfo.utcoffset(dt).total_seconds()
else:
raise ValueError("dt does not have timezone info")

Binary file not shown.

View File

@@ -27,7 +27,7 @@ import osxphotos
from ._constants import OSXPHOTOS_EXPORT_DB, SQLITE_CHECK_SAME_THREAD
from ._version import __version__
from .fileutil import FileUtil
from .utils import normalize_fs_path
from .unicode import normalize_fs_path
__all__ = [
"ExportDB",
@@ -921,32 +921,35 @@ class ExportDBInMemory(ExportDB):
returns: connection to the database
"""
if not os.path.isfile(dbfile):
conn = self._get_db_connection()
if not conn:
# database doesn't exist so create it in-memory
src = self._get_db_connection()
if not src:
raise Exception("Error getting connection to in-memory database")
self._create_or_migrate_db_tables(conn)
self._create_or_migrate_db_tables(src)
self.was_created = True
self.was_upgraded = ()
else:
conn = sqlite3.connect(dbfile, check_same_thread=SQLITE_CHECK_SAME_THREAD)
dbdump = self._dump_db(conn)
conn.close()
self.version = OSXPHOTOS_EXPORTDB_VERSION
return src
# Create a database in memory and import from the dump
conn = sqlite3.connect(
":memory:", check_same_thread=SQLITE_CHECK_SAME_THREAD
)
conn.cursor().executescript(dbdump.read())
self.was_created = False
version_info = self._get_database_version(conn)
if version_info[1] < OSXPHOTOS_EXPORTDB_VERSION:
self._create_or_migrate_db_tables(conn)
self.was_upgraded = (version_info[1], OSXPHOTOS_EXPORTDB_VERSION)
else:
self.was_upgraded = ()
# database exists so copy it to memory
src = sqlite3.connect(dbfile, check_same_thread=SQLITE_CHECK_SAME_THREAD)
# Create a database in memory by backing up the on-disk database
dst = sqlite3.connect(":memory:", check_same_thread=SQLITE_CHECK_SAME_THREAD)
with dst:
src.backup(dst, pages=1)
src.close()
self.was_created = False
version_info = self._get_database_version(dst)
if version_info[1] < OSXPHOTOS_EXPORTDB_VERSION:
self._create_or_migrate_db_tables(dst)
self.was_upgraded = (version_info[1], OSXPHOTOS_EXPORTDB_VERSION)
else:
self.was_upgraded = ()
self.version = OSXPHOTOS_EXPORTDB_VERSION
return conn
return dst
def _get_db_connection(self):
"""return db connection to in memory database"""

View File

@@ -9,9 +9,12 @@ import typing as t
from abc import ABC, abstractmethod
from tempfile import TemporaryDirectory
import Foundation
from .imageconverter import ImageConverter
from .platform import is_macos
from .unicode import normalize_fs_path
if is_macos:
import Foundation
__all__ = ["FileUtilABC", "FileUtilMacOS", "FileUtilShUtil", "FileUtil", "FileUtilNoOp"]
@@ -90,6 +93,9 @@ class FileUtilMacOS(FileUtilABC):
if src is None or dest is None:
raise ValueError("src and dest must not be None", src, dest)
src = normalize_fs_path(src)
dest = normalize_fs_path(dest)
if not os.path.isfile(src):
raise FileNotFoundError("src file does not appear to exist", src)
@@ -115,6 +121,9 @@ class FileUtilMacOS(FileUtilABC):
OSError if copy fails
TypeError if either path is None
"""
src = normalize_fs_path(src)
dest = normalize_fs_path(dest)
if not isinstance(src, pathlib.Path):
src = pathlib.Path(src)
@@ -135,6 +144,7 @@ class FileUtilMacOS(FileUtilABC):
@classmethod
def unlink(cls, filepath):
"""unlink filepath; if it's pathlib.Path, use Path.unlink, otherwise use os.unlink"""
filepath = normalize_fs_path(filepath)
if isinstance(filepath, pathlib.Path):
filepath.unlink()
else:
@@ -143,6 +153,7 @@ class FileUtilMacOS(FileUtilABC):
@classmethod
def rmdir(cls, dirpath):
"""remove directory filepath; dirpath must be empty"""
dirpath = normalize_fs_path(dirpath)
if isinstance(dirpath, pathlib.Path):
dirpath.rmdir()
else:
@@ -151,6 +162,7 @@ class FileUtilMacOS(FileUtilABC):
@classmethod
def utime(cls, path, times):
"""Set the access and modified time of path."""
path = normalize_fs_path(path)
os.utime(path, times=times)
@classmethod
@@ -166,6 +178,9 @@ class FileUtilMacOS(FileUtilABC):
Does not do a byte-by-byte comparison.
"""
f1 = normalize_fs_path(f1)
f2 = normalize_fs_path(f2)
s1 = cls._sig(os.stat(f1))
if mtime1 is not None:
s1 = (s1[0], s1[1], int(mtime1))
@@ -188,6 +203,7 @@ class FileUtilMacOS(FileUtilABC):
if not s2:
return False
f1 = normalize_fs_path(f1)
s1 = cls._sig(os.stat(f1))
if s1[0] != stat.S_IFREG or s2[0] != stat.S_IFREG:
return False
@@ -196,6 +212,7 @@ class FileUtilMacOS(FileUtilABC):
@classmethod
def file_sig(cls, f1):
"""return os.stat signature for file f1 as tuple of (mode, size, mtime)"""
f1 = normalize_fs_path(f1)
return cls._sig(os.stat(f1))
@classmethod
@@ -210,6 +227,8 @@ class FileUtilMacOS(FileUtilABC):
Returns:
True if success, otherwise False
"""
src_file = normalize_fs_path(src_file)
dest_file = normalize_fs_path(dest_file)
converter = ImageConverter()
return converter.write_jpeg(
src_file, dest_file, compression_quality=compression_quality
@@ -227,6 +246,8 @@ class FileUtilMacOS(FileUtilABC):
Name of renamed file (dest)
"""
src = normalize_fs_path(src)
dest = normalize_fs_path(dest)
os.rename(str(src), str(dest))
return dest
@@ -271,6 +292,9 @@ class FileUtilShUtil(FileUtilMacOS):
OSError if copy fails
TypeError if either path is None
"""
src = normalize_fs_path(src)
dest = normalize_fs_path(dest)
if not isinstance(src, pathlib.Path):
src = pathlib.Path(src)
@@ -288,7 +312,7 @@ class FileUtilShUtil(FileUtilMacOS):
return True
class FileUtil(FileUtilMacOS):
class FileUtil(FileUtilShUtil):
"""Various file utilities"""
pass

View File

@@ -5,16 +5,20 @@
# reference: https://stackoverflow.com/questions/59330149/coreimage-ciimage-write-jpg-is-shifting-colors-macos/59334308#59334308
import pathlib
import objc
import Metal
import Quartz
from Cocoa import NSURL
from Foundation import NSDictionary
import sys
# needed to capture system-level stderr
from wurlitzer import pipes
from .platform import is_macos
if is_macos:
import Metal
import objc
import Quartz
from Cocoa import NSURL
from Foundation import NSDictionary
__all__ = ["ImageConversionError", "ImageConverter"]

View File

@@ -1,7 +1,20 @@
""" utility functions for validating/sanitizing path components """
""" utility functions for validating/sanitizing path components
This module also performs Unicode normalization. For a quick summary, there are
multiple ways to write more complex characters in Unicode. This causes problems
when e.g. checking if a file already exists and you have multiple sources for
the same string with different encodings. This sadly happens in Photos, but
isn't a problem on macOS, since macOS does normalization behind-the-scenes (see
https://eclecticlight.co/2021/05/08/explainer-unicode-normalization-and-apfs/).
This causes problems on other platforms, so we normalize as part of filename
sanitization functions and rely on them being called every time a unique
filename is needed.
"""
import pathvalidate
from osxphotos.unicode import normalize_unicode
from ._constants import MAX_DIRNAME_LEN, MAX_FILENAME_LEN
__all__ = [
@@ -24,7 +37,7 @@ def is_valid_filepath(filepath):
def sanitize_filename(filename, replacement=":"):
"""replace any illegal characters in a filename and truncate filename if needed
"""replace any illegal characters in a filename, truncate filename if needed and normalize Unicode to NFC form
Args:
filename: str, filename to sanitze
@@ -35,6 +48,7 @@ def sanitize_filename(filename, replacement=":"):
"""
if filename:
filename = normalize_unicode(filename)
filename = filename.replace("/", replacement)
if len(filename) > MAX_FILENAME_LEN:
parts = filename.split(".")
@@ -54,7 +68,7 @@ def sanitize_filename(filename, replacement=":"):
def sanitize_dirname(dirname, replacement=":"):
"""replace any illegal characters in a directory name and truncate directory name if needed
"""replace any illegal characters in a directory name, truncate directory name if needed, and normalize Unicode to NFC form
Args:
dirname: str, directory name to sanitize
@@ -69,7 +83,7 @@ def sanitize_dirname(dirname, replacement=":"):
def sanitize_pathpart(pathpart, replacement=":"):
"""replace any illegal characters in a path part (either directory or filename without extension) and truncate name if needed
"""replace any illegal characters in a path part (either directory or filename without extension), truncate name if needed, and normalize Unicode to NFC form
Args:
pathpart: str, path part to sanitize
@@ -82,6 +96,7 @@ def sanitize_pathpart(pathpart, replacement=":"):
pathpart = (
pathpart.replace("/", replacement) if replacement is not None else pathpart
)
pathpart = normalize_unicode(pathpart)
if len(pathpart) > MAX_DIRNAME_LEN:
drop = len(pathpart) - MAX_DIRNAME_LEN
pathpart = pathpart[:-drop]

View File

@@ -1,6 +1,8 @@
""" PhotoExport class to export photos
"""
from __future__ import annotations
import dataclasses
import json
import logging
@@ -8,13 +10,11 @@ import os
import pathlib
import re
import typing as t
from collections import namedtuple # pylint: disable=syntax-error
from dataclasses import asdict, dataclass
from datetime import datetime
from enum import Enum
from types import SimpleNamespace
import photoscript
from mako.template import Template
from ._constants import (
@@ -35,15 +35,10 @@ from .datetime_utils import datetime_tz_to_utc
from .exiftool import ExifTool, ExifToolCaching, exiftool_can_write, get_exiftool_path
from .export_db import ExportDB, ExportDBTemp
from .fileutil import FileUtil
from .photokit import (
PHOTOS_VERSION_CURRENT,
PHOTOS_VERSION_ORIGINAL,
PHOTOS_VERSION_UNADJUSTED,
PhotoKitFetchFailed,
PhotoLibrary,
)
from .phototemplate import RenderOptions
from .platform import is_macos
from .rich_utils import add_rich_markup_tag
from .unicode import normalize_fs_path
from .uti import get_preferred_uti_extension
from .utils import (
hexdigest,
@@ -55,6 +50,18 @@ from .utils import (
unlock_filename,
)
if is_macos:
import photoscript
from .photokit import (
PHOTOS_VERSION_CURRENT,
PHOTOS_VERSION_ORIGINAL,
PHOTOS_VERSION_UNADJUSTED,
PhotoKitFetchFailed,
PhotoLibrary,
)
__all__ = [
"ExportError",
"ExportOptions",
@@ -69,6 +76,10 @@ if t.TYPE_CHECKING:
# retry if download_missing/use_photos_export fails the first time (which sometimes it does)
MAX_PHOTOSCRIPT_RETRIES = 3
# Global to hold the compiled XMP template
# This is expensive to compile so we only want to do it once
_global_xmp_template: Template | None = None
# return values for _should_update_photo
class ShouldUpdate(Enum):
@@ -237,6 +248,9 @@ class StagedFiles:
def __str__(self):
return str(self.asdict())
def __repr__(self):
return f"StagedFiles({self.asdict()})"
def asdict(self):
return {
"original": self.original,
@@ -472,6 +486,11 @@ class PhotoExporter:
dest, options = self._should_convert_to_jpeg(dest, options)
# stage files for export by finding path in local library or downloading from iCloud as appropriate
# for `--download-missing` and `--update` case, this may cause unnecessary downloads
# as it will download the file even if it's not needed (won't be checked until the _should_update_photo() call from _export_photo()
# fixing this will require major refactoring of the export code, see #1086
# leaving it for now as this should not be a common use case
# (if using `--update` it is much better to be using "Download originals to this Mac" in Photos)
staged_files = self._stage_photos_for_export(options)
src = staged_files.edited if options.edited else staged_files.original
@@ -658,8 +677,16 @@ class PhotoExporter:
return lock_filename(filename) if lock else filename
# if overwrite==False and #increment==False, export should fail if file exists
if dest.exists() and not any(
[options.increment, options.update, options.force_update, options.overwrite]
if (
not any(
[
options.increment,
options.update,
options.force_update,
options.overwrite,
]
)
and dest.exists()
):
raise FileExistsError(
f"destination exists ({dest}); overwrite={options.overwrite}, increment={options.increment}"
@@ -718,9 +745,18 @@ class PhotoExporter:
return dest
def _should_update_photo(
self, src: pathlib.Path, dest: pathlib.Path, options: ExportOptions
) -> t.Literal[True, False]:
"""Return True if photo should be updated, else False"""
self, src: pathlib.Path | None, dest: pathlib.Path, options: ExportOptions
) -> bool | ShouldUpdate:
"""Return True if photo should be updated, else False
Args:
src (pathlib.Path | None): source path; if None, photo is missing and
any checks that require src will return True
dest (pathlib.Path): destination path
Returns:
False if photo should not be updated otherwise a truthy ShouldUpdate value
"""
# NOTE: The order of certain checks is important
# read the comments below to understand why before changing
@@ -734,11 +770,11 @@ class PhotoExporter:
# photo doesn't exist in database, should update
return ShouldUpdate.NOT_IN_DATABASE
if options.export_as_hardlink and not dest.samefile(src):
if options.export_as_hardlink and (not src or not dest.samefile(src)):
# different files, should update
return ShouldUpdate.HARDLINK_DIFFERENT_FILES
if not options.export_as_hardlink and dest.samefile(src):
if not options.export_as_hardlink and (not src or dest.samefile(src)):
# same file but not exporting as hardlink, should update
return ShouldUpdate.NOT_HARDLINK_SAME_FILES
@@ -770,7 +806,9 @@ class PhotoExporter:
# as exiftool will be used to update edited file
return ShouldUpdate.EXIFTOOL_DIFFERENT if rv else False
if options.edited and not fileutil.cmp_file_sig(src, file_record.src_sig):
if options.edited and (
not src or not fileutil.cmp_file_sig(src, file_record.src_sig)
):
# edited file in Photos doesn't match what was last exported
return ShouldUpdate.EDITED_SIG_DIFFERENT
@@ -821,22 +859,46 @@ class PhotoExporter:
# download any missing files
if options.download_missing:
live_photo = staged.edited_live if options.edited else staged.original_live
missing_options = ExportOptions(
edited=options.edited,
preview=options.preview and not staged.preview,
raw_photo=options.raw_photo and not staged.raw,
live_photo=options.live_photo and not live_photo,
staged |= self._stage_missing_photos_for_export(
staged=staged, options=options
)
if options.use_photokit:
missing_staged = self._stage_photo_for_export_with_photokit(
options=missing_options
)
else:
missing_staged = self._stage_photo_for_export_with_applescript(
options=missing_options
)
staged |= missing_staged
return staged
def _stage_missing_photos_for_export(
self, staged: StagedFiles, options: ExportOptions
) -> StagedFiles:
"""Download and stage any missing files for export"""
# if live photo and requesting edited version need the edited live photo
live_photo = staged.edited_live if options.edited else staged.original_live
# is there actually a missing file? (#1086)
something_to_download = (
(self.photo.hasadjustments and options.edited and not staged.edited)
or (self.photo.live_photo and options.live_photo and not live_photo)
or (self.photo.has_raw and options.raw_photo and not staged.raw)
or (options.preview and not staged.preview)
or (not options.edited and not staged.original)
)
if not something_to_download:
return staged
missing_options = ExportOptions(
edited=options.edited,
preview=options.preview and not staged.preview,
raw_photo=self.photo.has_raw and options.raw_photo and not staged.raw,
live_photo=self.photo.live_photo and options.live_photo and not live_photo,
)
if options.use_photokit:
missing_staged = self._stage_photo_for_export_with_photokit(
options=missing_options
)
else:
missing_staged = self._stage_photo_for_export_with_applescript(
options=missing_options
)
staged |= missing_staged
return staged
def _stage_photo_for_export_with_photokit(
@@ -1181,7 +1243,7 @@ class PhotoExporter:
try:
fileutil.copy(src, dest_str)
verbose(
f"Exported {self._filename(self.photo.original_filename)} to {self._filepath(dest_str)}"
f"Exported {self._filename(self.photo.original_filename)} to {self._filepath(normalize_fs_path(dest_str))}"
)
except Exception as e:
raise ExportError(
@@ -1212,7 +1274,7 @@ class PhotoExporter:
# set data in the database
with export_db.create_or_get_file_record(dest_str, self.photo.uuid) as rec:
rec.photoinfo = self.photo.json()
rec.photoinfo = self.photo.json(shallow=True)
rec.export_options = options.bit_flags
# don't set src_sig as that is set above before any modifications by convert_to_jpeg or exiftool
if not options.ignore_signature:
@@ -1521,10 +1583,10 @@ class PhotoExporter:
if not options.dry_run:
warning_, error_ = self._write_exif_data(src, options=options)
if warning_:
exiftool_results.exiftool_warning.append((dest, warning_))
exiftool_results.exiftool_warning.append((str(dest), str(warning_)))
if error_:
exiftool_results.exiftool_error.append((dest, error_))
exiftool_results.error.append((dest, error_))
exiftool_results.exiftool_error.append((str(dest), str(error_)))
exiftool_results.error.append((str(dest), str(error_)))
exiftool_results.exif_updated.append(dest)
exiftool_results.to_touch.append(dest)
@@ -1953,10 +2015,7 @@ class PhotoExporter:
options = options or ExportOptions()
xmp_template_file = (
_XMP_TEMPLATE_NAME if not self.photo._db._beta else _XMP_TEMPLATE_NAME_BETA
)
xmp_template = Template(filename=os.path.join(_TEMPLATE_DIR, xmp_template_file))
xmp_template = self._xmp_template()
if extension is None:
extension = pathlib.Path(self.photo.original_filename)
@@ -2063,6 +2122,20 @@ class PhotoExporter:
xmp_str = "\n".join(line for line in xmp_str.split("\n") if line.strip() != "")
return xmp_str
def _xmp_template(self):
"""Return the mako template for XMP sidecar, creating it if necessary"""
global _global_xmp_template
if _global_xmp_template is not None:
return _global_xmp_template
xmp_template_file = (
_XMP_TEMPLATE_NAME_BETA if self.photo._db._beta else _XMP_TEMPLATE_NAME
)
_global_xmp_template = Template(
filename=os.path.join(_TEMPLATE_DIR, xmp_template_file)
)
return _global_xmp_template
def _write_sidecar(self, filename, sidecar_str):
"""write sidecar_str to filename
used for exporting sidecar info"""

View File

@@ -20,7 +20,6 @@ from types import SimpleNamespace
from typing import Any, Dict, Optional
import yaml
from osxmetadata import OSXMetaData
import osxphotos
@@ -59,15 +58,21 @@ from .exiftool import ExifToolCaching, get_exiftool_path
from .momentinfo import MomentInfo
from .personinfo import FaceInfo, PersonInfo
from .photoexporter import ExportOptions, PhotoExporter
from .phototables import PhotoTables
from .phototemplate import PhotoTemplate, RenderOptions
from .placeinfo import PlaceInfo4, PlaceInfo5
from .platform import assert_macos, is_macos
from .query_builder import get_query
from .scoreinfo import ScoreInfo
from .searchinfo import SearchInfo
from .text_detection import detect_text
from .uti import get_preferred_uti_extension, get_uti_for_extension
from .utils import _get_resource_loc, hexdigest, list_directory
if is_macos:
from osxmetadata import OSXMetaData
from .text_detection import detect_text
__all__ = ["PhotoInfo", "PhotoInfoNone", "frozen_photoinfo_factory"]
logger = logging.getLogger("osxphotos")
@@ -131,8 +136,7 @@ class PhotoInfo:
if not self.hasadjustments and self._db._db_version <= _PHOTOS_4_VERSION:
return None
imagedate = self._info["lastmodifieddate"]
if imagedate:
if imagedate := self._info["lastmodifieddate"]:
seconds = self._info["imageTimeZoneOffsetSeconds"] or 0
delta = timedelta(seconds=seconds)
tz = timezone(delta)
@@ -169,6 +173,9 @@ class PhotoInfo:
"""Returns candidate path for original photo on Photos >= version 5"""
if self._info["shared"]:
return self._path_5_shared()
if self.syndicated and not self.saved_to_library:
# path for "shared with you" syndicated photos that have not yet been saved to the library
return self._path_syndication()
return (
os.path.join(self._info["directory"], self._info["filename"])
if self._info["directory"].startswith("/")
@@ -208,6 +215,21 @@ class PhotoInfo:
filename,
)
def _path_syndication(self):
"""Return path for syndicated photo on Photos >= version 8"""
# Photos 8+ stores syndicated photos in a separate directory
# in ~/Photos Library.photoslibrary/scopes/syndication/originals/X/UUID.ext
# where X is first digit of UUID
syndication_path = "scopes/syndication/originals"
uuid_dir = self.uuid[0]
path = os.path.join(
self._db._library_path,
syndication_path,
uuid_dir,
self.filename,
)
return path if os.path.isfile(path) else None
def _path_4(self):
"""Returns candidate path for original photo on Photos <= version 4"""
if self._info["has_raw"]:
@@ -517,39 +539,26 @@ class PhotoInfo:
@property
def person_info(self):
"""list of PersonInfo objects for person in picture"""
try:
return self._personinfo
except AttributeError:
self._personinfo = [
PersonInfo(db=self._db, pk=pk) for pk in self._info["persons"]
]
return self._personinfo
return [PersonInfo(db=self._db, pk=pk) for pk in self._info["persons"]]
@property
def face_info(self):
"""list of FaceInfo objects for faces in picture"""
try:
return self._faceinfo
except AttributeError:
try:
faces = self._db._db_faceinfo_uuid[self._uuid]
self._faceinfo = [FaceInfo(db=self._db, pk=pk) for pk in faces]
except KeyError:
# no faces
self._faceinfo = []
return self._faceinfo
faces = self._db._db_faceinfo_uuid[self._uuid]
self._faceinfo = [FaceInfo(db=self._db, pk=pk) for pk in faces]
except KeyError:
# no faces
self._faceinfo = []
return self._faceinfo
@property
def moment_info(self):
"""Moment photo belongs to"""
try:
return self._moment
except AttributeError:
try:
self._moment = MomentInfo(db=self._db, moment_pk=self._info["momentID"])
except ValueError:
self._moment = None
return self._moment
return MomentInfo(db=self._db, moment_pk=self._info["momentID"])
except ValueError:
return None
@property
def albums(self):
@@ -566,65 +575,41 @@ class PhotoInfo:
@property
def burst_albums(self):
"""If photo is burst photo, list of albums it is contained in as well as any albums the key photo is contained in, otherwise returns self.albums"""
try:
return self._burst_albums
except AttributeError:
burst_albums = list(self.albums)
for photo in self.burst_photos:
if photo.burst_key:
burst_albums.extend(photo.albums)
self._burst_albums = list(set(burst_albums))
return self._burst_albums
burst_albums = list(self.albums)
for photo in self.burst_photos:
if photo.burst_key:
burst_albums.extend(photo.albums)
return list(set(burst_albums))
@property
def album_info(self):
"""list of AlbumInfo objects representing albums the photo is contained in"""
try:
return self._album_info
except AttributeError:
album_uuids = self._get_album_uuids()
self._album_info = [
AlbumInfo(db=self._db, uuid=album) for album in album_uuids
]
return self._album_info
album_uuids = self._get_album_uuids()
return [AlbumInfo(db=self._db, uuid=album) for album in album_uuids]
@property
def burst_album_info(self):
"""If photo is a burst photo, returns list of AlbumInfo objects representing albums the photo is contained in as well as albums the burst key photo is contained in, otherwise returns self.album_info."""
try:
return self._burst_album_info
except AttributeError:
burst_album_info = list(self.album_info)
for photo in self.burst_photos:
if photo.burst_key:
burst_album_info.extend(photo.album_info)
self._burst_album_info = list(set(burst_album_info))
return self._burst_album_info
burst_album_info = list(self.album_info)
for photo in self.burst_photos:
if photo.burst_key:
burst_album_info.extend(photo.album_info)
return list(set(burst_album_info))
@property
def import_info(self):
"""ImportInfo object representing import session for the photo or None if no import session"""
try:
return self._import_info
except AttributeError:
self._import_info = (
ImportInfo(db=self._db, uuid=self._info["import_uuid"])
if self._info["import_uuid"] is not None
else None
)
return self._import_info
return (
ImportInfo(db=self._db, uuid=self._info["import_uuid"])
if self._info["import_uuid"] is not None
else None
)
@property
def project_info(self):
"""list of AlbumInfo objects representing projects for the photo or None if no projects"""
try:
return self._project_info
except AttributeError:
project_uuids = self._get_album_uuids(project=True)
self._project_info = [
ProjectInfo(db=self._db, uuid=album) for album in project_uuids
]
return self._project_info
project_uuids = self._get_album_uuids(project=True)
return [ProjectInfo(db=self._db, uuid=album) for album in project_uuids]
@property
def keywords(self):
@@ -638,8 +623,7 @@ class PhotoInfo:
# in this case, return None so result is the same as if title had never been set (which returns NULL)
# issue #512
title = self._info["name"]
title = None if title == "" else title
return title
return None if title == "" else title
@property
def uuid(self):
@@ -912,33 +896,14 @@ class PhotoInfo:
photopath = None
if self._db._db_version <= _PHOTOS_4_VERSION:
if self.live_photo and not self.ismissing:
live_model_id = self._info["live_model_id"]
if live_model_id is None:
logger.debug(f"missing live_model_id: {self._uuid}")
photopath = None
else:
folder_id, file_id, nn_id = _get_resource_loc(live_model_id)
library_path = self._db.library_path
photopath = os.path.join(
library_path,
"resources",
"media",
"master",
folder_id,
nn_id,
f"jpegvideocomplement_{file_id}.mov",
)
if not os.path.isfile(photopath):
# In testing, I've seen occasional missing movie for live photo
# These appear to be valid -- e.g. live component hasn't been downloaded from iCloud
# photos 4 has "isOnDisk" column we could check
# or could do the actual check with "isfile"
# TODO: should this be a warning or debug?
photopath = None
else:
photopath = None
return self._path_live_photo_4()
elif self.live_photo and self.path and not self.ismissing:
if self.shared:
return self._path_live_photo_shared_5()
if self.syndicated and not self.saved_to_library:
# syndicated ("Shared with you") photos not yet saved to library
return self._path_live_syndicated()
filename = pathlib.Path(self.path)
photopath = filename.parent.joinpath(f"{filename.stem}_3.mov")
photopath = str(photopath)
@@ -952,8 +917,68 @@ class PhotoInfo:
return photopath
def _path_live_photo_shared_5(self):
"""Return path for live photo for shared photos"""
if not self.shared:
raise ValueError(f"photo {self.uuid} is not a shared photo")
if not self.live_photo:
raise ValueError(f"photo {self.uuid} is not a live photo")
photopath = self._path_5_shared()
if photopath:
photopath = pathlib.Path(photopath).with_suffix(".MOV")
if not photopath.exists():
photopath = None
return photopath
def _path_live_photo_4(self):
"""Return path for live edited photo for Photos <= 4"""
if self.live_photo and not self.ismissing:
live_model_id = self._info["live_model_id"]
if live_model_id is None:
logger.debug(f"missing live_model_id: {self._uuid}")
photopath = None
else:
folder_id, file_id, nn_id = _get_resource_loc(live_model_id)
library_path = self._db.library_path
photopath = os.path.join(
library_path,
"resources",
"media",
"master",
folder_id,
nn_id,
f"jpegvideocomplement_{file_id}.mov",
)
if not os.path.isfile(photopath):
# In testing, I've seen occasional missing movie for live photo
# These appear to be valid -- e.g. live component hasn't been downloaded from iCloud
# photos 4 has "isOnDisk" column we could check
# or could do the actual check with "isfile"
# TODO: should this be a warning or debug?
photopath = None
else:
photopath = None
return photopath
def _path_live_syndicated(self):
"""Return path for live syndicated photo on Photos >= version 8"""
# Photos 8+ stores live syndicated photos in a separate directory
# in ~/Photos Library.photoslibrary/scopes/syndication/originals/X/UUID_3.mov
# where X is first digit of UUID
syndication_path = "scopes/syndication/originals"
uuid_dir = self.uuid[0]
filename = f"{pathlib.Path(self.filename).stem}_3.mov"
live_photo = os.path.join(
self._db._library_path,
syndication_path,
uuid_dir,
filename,
)
return live_photo if os.path.isfile(live_photo) else None
@cached_property
def path_derivatives(self):
def path_derivatives(self) -> list[str]:
"""Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first)"""
if self._db._db_version <= _PHOTOS_4_VERSION:
return self._path_derivatives_4()
@@ -962,24 +987,36 @@ class PhotoInfo:
return self._path_derivatives_5_shared()
directory = self._uuid[0] # first char of uuid
derivative_path = (
pathlib.Path(self._db._library_path) / f"resources/derivatives/{directory}"
)
if self.syndicated and not self.saved_to_library:
# syndicated ("Shared with you") photos not yet saved to library
derivative_path = "scopes/syndication/resources/derivatives"
thumb_path = (
f"{derivative_path}/masters/{directory}/{self.uuid}_4_5005_c.jpeg"
)
else:
derivative_path = f"resources/derivatives/{directory}"
thumb_path = (
f"resources/derivatives/masters/{directory}/{self.uuid}_4_5005_c.jpeg"
)
derivative_path = pathlib.Path(self._db._library_path).joinpath(derivative_path)
thumb_path = pathlib.Path(self._db._library_path).joinpath(thumb_path)
# find all files that start with uuid in derivative path
files = list(derivative_path.glob(f"{self.uuid}*.*"))
# previews may be missing from derivatives path
# there are what appear to be low res thumbnails in the "masters" subfolder
thumb_path = (
pathlib.Path(self._db._library_path)
/ f"resources/derivatives/masters/{directory}/{self.uuid}_4_5005_c.jpeg"
)
if thumb_path.exists():
files.append(thumb_path)
# sort by file size, largest first
files = sorted(files, reverse=True, key=lambda f: f.stat().st_size)
# return list of filename but skip .THM files (these are actually low-res thumbnails in JPEG format but with .THM extension)
derivatives = [str(filename) for filename in files if filename.suffix != ".THM"]
if self.isphoto and len(derivatives) > 1 and derivatives[0].endswith(".mov"):
# ensure .mov is first in list as poster image could be larger than the movie preview
derivatives[1], derivatives[0] = derivatives[0], derivatives[1]
return derivatives
@@ -1298,6 +1335,38 @@ class PhotoInfo:
self._search_info_normalized = SearchInfo(self, normalized=True)
return self._search_info_normalized
@cached_property
def syndicated(self) -> bool | None:
"""Return true if photo was shared via syndication (e.g. via Messages, etc.);
these are photos that appear in "Shared with you" album.
Photos 8+ only; returns None if not Photos 8+.
"""
if self._db.photos_version < 8:
return None
try:
return (
self._db._db_syndication_uuid[self.uuid]["syndication_identifier"]
is not None
)
except KeyError:
return False
@cached_property
def saved_to_library(self) -> bool | None:
"""Return True if syndicated photo has been saved to library;
returns False if photo is not syndicated or has not been saved to the library.
Returns None if not Photos 8+.
Syndicated photos are photos that appear in "Shared with you" album; Photos 8+ only.
"""
if self._db.photos_version < 8:
return None
try:
return self._db._db_syndication_uuid[self.uuid]["syndication_history"] != 0
except KeyError:
return False
@property
def labels(self):
"""returns list of labels applied to photo by Photos image categorization
@@ -1498,6 +1567,8 @@ class PhotoInfo:
def _detected_text(self):
"""detect text in photo, either from cached extended attribute or by attempting text detection"""
assert_macos()
path = (
self.path_edited if self.hasadjustments and self.path_edited else self.path
)
@@ -1777,41 +1848,31 @@ class PhotoInfo:
}
return yaml.dump(info, sort_keys=False)
def asdict(self):
"""return dict representation"""
def asdict(self, shallow: bool = True) -> dict[str, Any]:
"""Return dict representation of PhotoInfo object.
Args:
shallow: if True, return shallow representation (does not contain folder_info, person_info, etc.)
Returns:
dict representation of PhotoInfo object
Note:
The shallow representation is used internally by export as it contains only the subset of data needed for export.
"""
adjustments = self.adjustments.asdict() if self.adjustments else {}
album_info = [album.asdict() for album in self.album_info]
burst_album_info = [a.asdict() for a in self.burst_album_info]
burst_photos = [p.uuid for p in self.burst_photos]
comments = [comment.asdict() for comment in self.comments]
exif_info = dataclasses.asdict(self.exif_info) if self.exif_info else {}
face_info = [face.asdict() for face in self.face_info]
folders = {album.title: album.folder_names for album in self.album_info}
import_info = self.import_info.asdict() if self.import_info else {}
likes = [like.asdict() for like in self.likes]
person_info = [p.asdict() for p in self.person_info]
place = self.place.asdict() if self.place else {}
project_info = [p.asdict() for p in self.project_info]
score = dataclasses.asdict(self.score) if self.score else {}
search_info = self.search_info.asdict() if self.search_info else {}
search_info_normalized = (
self.search_info_normalized.asdict() if self.search_info_normalized else {}
)
return {
"adjustments": adjustments,
"album_info": album_info,
dict_data = {
"albums": self.albums,
"burst_album_info": burst_album_info,
"burst_albums": self.burst_albums,
"burst_default_pick": self.burst_default_pick,
"burst_key": self.burst_key,
"burst_photos": burst_photos,
"burst_selected": self.burst_selected,
"burst": self.burst,
"cloud_guid": self.cloud_guid,
"cloud_metadata": self.cloud_metadata,
"cloud_owner_hashed_id": self.cloud_owner_hashed_id,
"comments": comments,
"date_added": self.date_added,
@@ -1831,7 +1892,6 @@ class PhotoInfo:
"hdr": self.hdr,
"height": self.height,
"hidden": self.hidden,
"import_info": import_info,
"incloud": self.incloud,
"intrash": self.intrash,
"iscloudasset": self.iscloudasset,
@@ -1841,7 +1901,6 @@ class PhotoInfo:
"israw": self.israw,
"isreference": self.isreference,
"keywords": self.keywords,
"labels_normalized": self.labels_normalized,
"labels": self.labels,
"latitude": self._latitude,
"library": self._db._library_path,
@@ -1857,22 +1916,17 @@ class PhotoInfo:
"original_width": self.original_width,
"owner": self.owner,
"panorama": self.panorama,
"path_derivatives": self.path_derivatives,
"path_edited_live_photo": self.path_edited_live_photo,
"path_edited": self.path_edited,
"path_live_photo": self.path_live_photo,
"path_raw": self.path_raw,
"path": self.path,
"person_info": person_info,
"persons": self.persons,
"place": place,
"portrait": self.portrait,
"project_info": project_info,
"raw_original": self.raw_original,
"score": score,
"screenshot": self.screenshot,
"search_info_normalized": search_info_normalized,
"search_info": search_info,
"selfie": self.selfie,
"shared": self.shared,
"slow_mo": self.slow_mo,
@@ -1888,14 +1942,57 @@ class PhotoInfo:
"width": self.width,
}
def json(self):
"""Return JSON representation"""
# non-shallow keys
if not shallow:
dict_data["album_info"] = [album.asdict() for album in self.album_info]
dict_data["path_derivatives"] = self.path_derivatives
dict_data["adjustments"] = (
self.adjustments.asdict() if self.adjustments else {}
)
dict_data["burst_album_info"] = [a.asdict() for a in self.burst_album_info]
dict_data["burst_albums"] = self.burst_albums
dict_data["burst_default_pick"] = self.burst_default_pick
dict_data["burst_key"] = self.burst_key
dict_data["burst_photos"] = [p.uuid for p in self.burst_photos]
dict_data["burst_selected"] = self.burst_selected
dict_data["cloud_metadata"] = self.cloud_metadata
dict_data["import_info"] = (
self.import_info.asdict() if self.import_info else {}
)
dict_data["labels_normalized"] = self.labels_normalized
dict_data["person_info"] = [p.asdict() for p in self.person_info]
dict_data["project_info"] = [p.asdict() for p in self.project_info]
dict_data["search_info"] = (
self.search_info.asdict() if self.search_info else {}
)
dict_data["search_info_normalized"] = (
self.search_info_normalized.asdict()
if self.search_info_normalized
else {}
)
return dict_data
def json(self, indent: int | None = None, shallow: bool = True) -> str:
"""Return JSON representation
Args:
indent: indent level for JSON, if None, no indent
shallow: if True, return shallow JSON representation (does not contain folder_info, person_info, etc.)
Returns:
JSON string
Note:
The shallow representation is used internally by export as it contains only the subset of data needed for export.
"""
def default(o):
if isinstance(o, (datetime.date, datetime.datetime)):
return o.isoformat()
dict_data = self.asdict()
dict_data = self.asdict(shallow=True) if shallow else self.asdict(shallow=False)
for k, v in dict_data.items():
# sort lists such as keywords so JSON is consistent
# but do not sort certain items like location
@@ -1903,7 +2000,11 @@ class PhotoInfo:
continue
if v and isinstance(v, (list, tuple)) and not isinstance(v[0], dict):
dict_data[k] = sorted(v, key=lambda v: v if v is not None else "")
return json.dumps(dict_data, sort_keys=True, default=default)
return json.dumps(dict_data, sort_keys=True, default=default, indent=indent)
def tables(self) -> PhotoTables:
"""Return PhotoTables object to provide access database tables associated with this photo (Photos 5+)"""
return None if self._db._photos_ver < 5 else PhotoTables(self)
def _json_hexdigest(self):
"""JSON for use by hexdigest()"""
@@ -1917,15 +2018,9 @@ class PhotoInfo:
if isinstance(o, (datetime.date, datetime.datetime)):
return o.isoformat()
dict_data = self.asdict()
dict_data = self.asdict(shallow=True)
for k in [
"album_info",
"burst_album_info",
"face_info",
"person_info",
"visible",
]:
for k in ["face_info", "visible"]:
del dict_data[k]
for k, v in dict_data.items():

View File

@@ -19,9 +19,14 @@
import copy
import pathlib
import sys
import threading
import time
from .platform import get_macos_version
assert sys.platform == "darwin"
import AVFoundation
import CoreServices
import Foundation
@@ -34,7 +39,7 @@ from wurlitzer import pipes
from .fileutil import FileUtil
from .uti import get_preferred_uti_extension
from .utils import get_macos_version, increment_filename
from .utils import increment_filename
__all__ = [
"NSURL_to_path",
@@ -84,6 +89,7 @@ PHOTOKIT_NOTIFICATION_FINISHED_REQUEST = "PyPhotoKitNotificationFinishedRequest"
# minimum amount to sleep while waiting for export
MIN_SLEEP = 0.015
### utility functions
def NSURL_to_path(url):
"""Convert URL string as represented by NSURL to a path string"""
@@ -626,7 +632,8 @@ class PhotoAsset:
def handler(imageData, dataUTI, orientation, info):
"""result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
all returned by the request is set as properties of nonlocal data (Fetchdata object)"""
all returned by the request is set as properties of nonlocal data (Fetchdata object)
"""
nonlocal requestdata
@@ -673,7 +680,8 @@ class PhotoAsset:
def handler(data):
"""result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
all returned by the request is set as properties of nonlocal data (Fetchdata object)"""
all returned by the request is set as properties of nonlocal data (Fetchdata object)
"""
nonlocal requestdata
@@ -710,7 +718,8 @@ class PhotoAsset:
def handler(imageData, dataUTI, orientation, info):
"""result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
all returned by the request is set as properties of nonlocal data (Fetchdata object)"""
all returned by the request is set as properties of nonlocal data (Fetchdata object)
"""
nonlocal data

View File

@@ -1,30 +1,54 @@
""" PhotosAlbum class to create an album in default Photos library and add photos to it """
from __future__ import annotations
import unicodedata
from typing import List, Optional
import photoscript
from more_itertools import chunked
from photoscript import Album, Folder, Photo, PhotosLibrary
from .photoinfo import PhotoInfo
from .platform import assert_macos
from .utils import noop, pluralize
assert_macos()
import photoscript
from photoscript import Album, Folder, Photo, PhotosLibrary
__all__ = ["PhotosAlbum", "PhotosAlbumPhotoScript"]
def get_unicode_variants(s: str) -> list[str]:
"""Get all unicode variants of string"""
variants = []
for form in ["NFC", "NFD", "NFKC", "NFKD"]:
normalized = unicodedata.normalize(form, s)
variants.append(normalized)
return variants
def folder_by_path(folders: List[str], verbose: Optional[callable] = None) -> Folder:
"""Get (and create if necessary) a Photos Folder by path (passed as list of folder names)"""
library = PhotosLibrary()
verbose = verbose or noop
top_folder_name = folders.pop(0)
top_folder = library.folder(top_folder_name, top_level=True)
if not top_folder:
for folder_variant in get_unicode_variants(top_folder_name):
top_folder = library.folder(folder_variant, top_level=True)
if top_folder is not None:
break
else:
verbose(f"Creating folder '{top_folder_name}'")
top_folder = library.create_folder(top_folder_name)
current_folder = top_folder
for folder_name in folders:
folder = current_folder.folder(folder_name)
if not folder:
for folder_variant in get_unicode_variants(folder_name):
folder = current_folder.folder(folder_variant)
if folder is not None:
break
else:
verbose(f"Creating folder '{folder_name}'")
folder = current_folder.create_folder(folder_name)
current_folder = folder
@@ -41,15 +65,24 @@ def album_by_path(
# have folders
album_name = folders_album.pop()
folder = folder_by_path(folders_album, verbose)
album = folder.album(album_name)
if album is None:
for album_variant in get_unicode_variants(album_name):
# Get album if it exists
# need to check every unicode variant to avoid creating duplicate albums with same visual representation (#1085)
album = folder.album(album_variant)
if album is not None:
break
else:
verbose(f"Creating album '{album_name}'")
album = folder.create_album(album_name)
else:
# only have album name
album_name = folders_album[0]
album = library.album(album_name, top_level=True)
if album is None:
for album_variant in get_unicode_variants(album_name):
album = library.album(album_variant, top_level=True)
if album is not None:
break
else:
# album doesn't exist, create it
verbose(f"Creating album '{album_name}'")
album = library.create_album(album_name)

View File

@@ -4,6 +4,10 @@ from __future__ import annotations
import sqlite3
from .platform import assert_macos
assert_macos()
import photoscript
from ._constants import _DB_TABLE_NAMES, _PHOTOS_5_ALBUM_KIND, _PHOTOS_5_FOLDER_KIND

View File

@@ -4,4 +4,4 @@ Processes a Photos.app library database to extract information about photos
"""
from .photosdb import PhotosDB
from .photosdb_utils import get_db_version, get_db_model_version, get_model_version
from .photosdb_utils import get_db_model_version, get_db_version, get_model_version

View File

@@ -6,8 +6,8 @@ import datetime
from dataclasses import dataclass
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION, TIME_DELTA
from ..utils import normalize_unicode
from ..sqlite_utils import sqlite_open_ro
from ..unicode import normalize_unicode
def _process_comments(self):

View File

@@ -4,7 +4,7 @@
import logging
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION
from ..sqlite_utils import sqlite_open_ro, sqlite_db_is_locked
from ..sqlite_utils import sqlite_db_is_locked, sqlite_open_ro
from .photosdb_utils import get_db_version

View File

@@ -4,7 +4,7 @@
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION
from ..sqlite_utils import sqlite_open_ro
from ..utils import normalize_unicode
from ..unicode import normalize_unicode
from .photosdb_utils import get_db_version
"""
@@ -309,7 +309,9 @@ def _process_faceinfo_5(photosdb):
face["righteyey"] = row[34]
face["roll"] = row[35]
face["size"] = row[36]
face["yaw"] = 0 # Photos 4 only (this is in Photos 5-7, but dropped in Ventura so just don't support it)
face[
"yaw"
] = 0 # Photos 4 only (this is in Photos 5-7, but dropped in Ventura so just don't support it)
face["pitch"] = 0 # not defined in Photos 5
photosdb._db_faceinfo_pk[pk] = face

View File

@@ -7,11 +7,10 @@ import logging
import pathlib
import uuid as uuidlib
from functools import lru_cache
from pprint import pformat
from .._constants import _PHOTOS_4_VERSION, search_category_factory
from ..sqlite_utils import sqlite_db_is_locked, sqlite_open_ro
from ..utils import normalize_unicode
from ..unicode import normalize_unicode
"""
This module should be imported in the class defintion of PhotosDB in photosdb.py
@@ -154,7 +153,7 @@ def _process_searchinfo(self):
def labels(self):
"""return list of all search info labels found in the library"""
if self._db_version <= _PHOTOS_4_VERSION:
logging.warning(f"SearchInfo not implemented for this library version")
logging.warning("SearchInfo not implemented for this library version")
return []
return list(self._db_searchinfo_labels.keys())
@@ -164,7 +163,7 @@ def labels(self):
def labels_normalized(self):
"""return list of all normalized search info labels found in the library"""
if self._db_version <= _PHOTOS_4_VERSION:
logging.warning(f"SearchInfo not implemented for this library version")
logging.warning("SearchInfo not implemented for this library version")
return []
return list(self._db_searchinfo_labels_normalized.keys())
@@ -174,8 +173,8 @@ def labels_normalized(self):
def labels_as_dict(self):
"""return labels as dict of label: count in reverse sorted order (descending)"""
if self._db_version <= _PHOTOS_4_VERSION:
logging.warning(f"SearchInfo not implemented for this library version")
return dict()
logging.warning("SearchInfo not implemented for this library version")
return {}
labels = {k: len(v) for k, v in self._db_searchinfo_labels.items()}
labels = dict(sorted(labels.items(), key=lambda kv: kv[1], reverse=True))
@@ -186,8 +185,8 @@ def labels_as_dict(self):
def labels_normalized_as_dict(self):
"""return normalized labels as dict of label: count in reverse sorted order (descending)"""
if self._db_version <= _PHOTOS_4_VERSION:
logging.warning(f"SearchInfo not implemented for this library version")
return dict()
logging.warning("SearchInfo not implemented for this library version")
return {}
labels = {k: len(v) for k, v in self._db_searchinfo_labels_normalized.items()}
labels = dict(sorted(labels.items(), key=lambda kv: kv[1], reverse=True))
return labels

View File

@@ -0,0 +1,60 @@
""" Methods for PhotosDB to process Syndication info (#1054) """
from __future__ import annotations
from typing import TYPE_CHECKING
from .._constants import _DB_TABLE_NAMES
from ..sqlite_utils import sqlite_open_ro
if TYPE_CHECKING:
from osxphotos.photosdb import PhotosDB
def _process_syndicationinfo(self: PhotosDB):
"""Process syndication information"""
self._db_syndication_uuid = {}
if self.photos_version < 8:
raise NotImplementedError(
f"syndication info not implemented for this database version: {self.photos_version}"
)
else:
_process_syndicationinfo_8(self)
def _process_syndicationinfo_8(photosdb: PhotosDB):
"""Process Syndication info for Photos 8.0 and later
Args:
photosdb: an OSXPhotosDB instance
"""
db = photosdb._tmp_db
zasset = _DB_TABLE_NAMES[photosdb._photos_ver]["ASSET"]
(conn, cursor) = sqlite_open_ro(db)
result = cursor.execute(
f"""
SELECT
{zasset}.ZUUID,
{zasset}.ZSYNDICATIONSTATE,
ZADDITIONALASSETATTRIBUTES.ZSYNDICATIONHISTORY,
ZADDITIONALASSETATTRIBUTES.ZSYNDICATIONIDENTIFIER
FROM {zasset}
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {zasset}.Z_PK
"""
)
for row in result:
uuid = row[0]
syndication_state = row[1]
syndication_history = row[2]
syndication_identifier = row[3]
photosdb._db_syndication_uuid[uuid] = {
"syndication_state": syndication_state,
"syndication_identifier": syndication_identifier,
"syndication_history": syndication_history,
}

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