Compare commits
28 Commits
feature_co
...
RhetTbull-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1a71bba1e | ||
|
|
0c85298c03 | ||
|
|
b4b58d3b00 | ||
|
|
ed543aa2d0 | ||
|
|
1b0c91db97 | ||
|
|
dd3914328b | ||
|
|
4b4252a73c | ||
|
|
956cecfa30 | ||
|
|
6f88f19950 | ||
|
|
ce4f3c4c0b | ||
|
|
3993cf220d | ||
|
|
2ae41a5a7a | ||
|
|
519a6b0035 | ||
|
|
a4b4f1c288 | ||
|
|
f668ecf0c4 | ||
|
|
058b56092f | ||
|
|
d2b7783125 | ||
|
|
f4a743468d | ||
|
|
adac985309 | ||
|
|
4939324d2f | ||
|
|
1a3b4c2afe | ||
|
|
1b16a39cef | ||
|
|
0d31a152be | ||
|
|
34fbd87fc6 | ||
|
|
3ddfea6a3d | ||
|
|
8b59615ff7 | ||
|
|
c590a16d70 | ||
|
|
36ce86c4a6 |
@@ -262,7 +262,8 @@
|
||||
"bug",
|
||||
"ideas",
|
||||
"test",
|
||||
"code"
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -529,7 +530,44 @@
|
||||
"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"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.59.0
|
||||
current_version = 0.59.3
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
|
||||
serialize = {major}.{minor}.{patch}
|
||||
|
||||
|
||||
11
.github/workflows/tests.yml
vendored
11
.github/workflows/tests.yml
vendored
@@ -3,13 +3,22 @@ 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]')"
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
os: [macos-latest]
|
||||
os: [macos-latest-xl]
|
||||
python-version: ['3.9', '3.10', '3.11']
|
||||
|
||||
steps:
|
||||
|
||||
@@ -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)
|
||||
@@ -1395,6 +1396,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 +2164,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 +2494,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.59.3'|
|
||||
|{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|
|
||||
|
||||
63
CHANGELOG.md
63
CHANGELOG.md
@@ -2,6 +2,69 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
16
README.md
16
README.md
@@ -7,7 +7,7 @@
|
||||
[](https://pepy.tech/project/osxphotos)
|
||||
[](https://www.reddit.com/r/osxphotos/)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors)
|
||||
[](#contributors)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
OSXPhotos provides the ability to interact with and query Apple's Photos.app library on macOS. You can query the Photos library database — for example, file name, file path, and metadata such as keywords/tags, persons/faces, albums, etc. You can also easily export both the original and edited photos.
|
||||
@@ -2094,7 +2094,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.59.3'
|
||||
{osxphotos_cmd_line} The full command line used to run osxphotos
|
||||
|
||||
The following substitutions may result in multiple values. Thus if specified
|
||||
@@ -2581,7 +2581,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.59.3'|
|
||||
|{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|
|
||||
@@ -2665,7 +2665,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 +2701,13 @@ 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></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: c9decb5b6d226b940d02770bb432ca41
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../genindex.html" /><link rel="search" title="Search" href="../search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
|
||||
<title>Overview: module code - osxphotos 0.59.0 documentation</title>
|
||||
<title>Overview: module code - osxphotos 0.59.3 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../_static/styles/furo.css?digest=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.3 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -146,7 +146,7 @@
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="../index.html">
|
||||
|
||||
|
||||
<span class="sidebar-brand-text">osxphotos 0.59.0 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.59.3 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="../search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -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.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">
|
||||
|
||||
@@ -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"> """</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">"values and sort_keys must have same length"</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">"values and sort_keys must be same length"</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">"""Return album info as a dict"""</span>
|
||||
<span class="sd">"""Return album info as a dict; does not include photos"""</span>
|
||||
<span class="k">return</span> <span class="p">{</span>
|
||||
<span class="s2">"uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
|
||||
<span class="s2">"creation_date"</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">"start_date"</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">"end_date"</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">"owner"</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">"photos"</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">"""Return album info as a dict"""</span>
|
||||
<span class="sd">"""Return album info as a dict; does not include photos"""</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">"title"</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">"folder_names"</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">"""Return import info as a dict"""</span>
|
||||
<span class="sd">"""Return import info as a dict; does not include photos"""</span>
|
||||
<span class="k">return</span> <span class="p">{</span>
|
||||
<span class="s2">"uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
|
||||
<span class="s2">"creation_date"</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">"start_date"</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">"end_date"</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">"title"</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">"photos"</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"> """</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">"""Return hierarchical list of folders the album is contained in</span>
|
||||
<span class="sd"> the folder list is in form:</span>
|
||||
<span class="sd"> ["Top level folder", "sub folder 1", "sub folder 2", ...]</span>
|
||||
<span class="sd"> or empty list if album is not in any folders</span>
|
||||
<span class="sd"> """</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">"""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"> """</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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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.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">
|
||||
|
||||
@@ -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.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.58.1 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.58.1 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">
|
||||
|
||||
@@ -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.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">
|
||||
@@ -1409,7 +1409,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'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 +1718,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>
|
||||
|
||||
@@ -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.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">
|
||||
@@ -714,39 +714,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">"""list of PersonInfo objects for person in picture"""</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">"persons"</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">"persons"</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">"""list of FaceInfo objects for faces in picture"""</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">"""Moment photo belongs to"""</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">"momentID"</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">"momentID"</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 +750,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">"""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"""</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">"""list of AlbumInfo objects representing albums the photo is contained in"""</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">"""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."""</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">"""ImportInfo object representing import session for the photo or None if no import session"""</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">"import_uuid"</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">"import_uuid"</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">"import_uuid"</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">"import_uuid"</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">"""list of AlbumInfo objects representing projects for the photo or None if no projects"""</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 +798,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">"name"</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">""</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">""</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>
|
||||
@@ -1974,41 +1936,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">"""return dict representation"""</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">-></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">"""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"> """</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">"adjustments"</span><span class="p">:</span> <span class="n">adjustments</span><span class="p">,</span>
|
||||
<span class="s2">"album_info"</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">"albums"</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">"burst_album_info"</span><span class="p">:</span> <span class="n">burst_album_info</span><span class="p">,</span>
|
||||
<span class="s2">"burst_albums"</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">"burst_default_pick"</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">"burst_key"</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">"burst_photos"</span><span class="p">:</span> <span class="n">burst_photos</span><span class="p">,</span>
|
||||
<span class="s2">"burst_selected"</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">"burst"</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">"cloud_guid"</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">"cloud_metadata"</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">"cloud_owner_hashed_id"</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">"comments"</span><span class="p">:</span> <span class="n">comments</span><span class="p">,</span>
|
||||
<span class="s2">"date_added"</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 +1980,6 @@
|
||||
<span class="s2">"hdr"</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">"height"</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">"hidden"</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">"import_info"</span><span class="p">:</span> <span class="n">import_info</span><span class="p">,</span>
|
||||
<span class="s2">"incloud"</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">"intrash"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">intrash</span><span class="p">,</span>
|
||||
<span class="s2">"iscloudasset"</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 +1989,6 @@
|
||||
<span class="s2">"israw"</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">"isreference"</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">"keywords"</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">"labels_normalized"</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">"labels"</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">"latitude"</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">"library"</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 +2004,17 @@
|
||||
<span class="s2">"original_width"</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">"owner"</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">"panorama"</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">"path_derivatives"</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">"path_edited_live_photo"</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">"path_edited"</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">"path_live_photo"</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">"path_raw"</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">"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="s2">"person_info"</span><span class="p">:</span> <span class="n">person_info</span><span class="p">,</span>
|
||||
<span class="s2">"persons"</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">"place"</span><span class="p">:</span> <span class="n">place</span><span class="p">,</span>
|
||||
<span class="s2">"portrait"</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">"project_info"</span><span class="p">:</span> <span class="n">project_info</span><span class="p">,</span>
|
||||
<span class="s2">"raw_original"</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">"score"</span><span class="p">:</span> <span class="n">score</span><span class="p">,</span>
|
||||
<span class="s2">"screenshot"</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">"search_info_normalized"</span><span class="p">:</span> <span class="n">search_info_normalized</span><span class="p">,</span>
|
||||
<span class="s2">"search_info"</span><span class="p">:</span> <span class="n">search_info</span><span class="p">,</span>
|
||||
<span class="s2">"selfie"</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">"shared"</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">"slow_mo"</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 +2028,59 @@
|
||||
<span class="s2">"uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
|
||||
<span class="s2">"visible"</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">"width"</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">"""Return JSON representation"""</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">"album_info"</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">"path_derivatives"</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">"adjustments"</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">"burst_album_info"</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">"burst_albums"</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">"burst_default_pick"</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">"burst_key"</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">"burst_photos"</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">"burst_selected"</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">"cloud_metadata"</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">"import_info"</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">"labels_normalized"</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">"person_info"</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">"project_info"</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">"search_info"</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">"search_info_normalized"</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">-></span> <span class="nb">str</span><span class="p">:</span>
|
||||
<span class="sd">"""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"> """</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 +2088,7 @@
|
||||
<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">""</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>
|
||||
|
||||
<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">"""JSON for use by hexdigest()"""</span>
|
||||
@@ -2114,15 +2102,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">"album_info"</span><span class="p">,</span>
|
||||
<span class="s2">"burst_album_info"</span><span class="p">,</span>
|
||||
<span class="s2">"face_info"</span><span class="p">,</span>
|
||||
<span class="s2">"person_info"</span><span class="p">,</span>
|
||||
<span class="s2">"visible"</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">"face_info"</span><span class="p">,</span> <span class="s2">"visible"</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>
|
||||
|
||||
@@ -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.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.58.1 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.58.1 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">
|
||||
|
||||
@@ -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.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">
|
||||
@@ -713,7 +713,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">"""return list of AlbumInfo objects for each shared album in the photos database</span>
|
||||
<span class="sd"> only valid for Photos 5; on Photos <= 4, prints warning and returns empty list"""</span>
|
||||
<span class="sd"> only valid for Photos 5; on Photos <= 4, prints warning and returns empty list</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="c1"># if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it'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 +741,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">"""return list of shared albums found in photos database</span>
|
||||
<span class="sd"> only valid for Photos 5; on Photos <= 4, prints warning and returns empty list"""</span>
|
||||
<span class="sd"> only valid for Photos 5; on Photos <= 4, prints warning and returns empty list</span>
|
||||
<span class="sd"> """</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>
|
||||
@@ -3250,7 +3252,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">"""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 +3264,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"> """</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">-></span> <span class="n">List</span><span class="p">[</span><span class="n">PhotoInfo</span><span class="p">]:</span>
|
||||
<span class="sd">"""Run a query against PhotosDB to extract the photos based on user supplied options</span>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
|
||||
<title>osxphotos.phototemplate - osxphotos 0.59.0 documentation</title>
|
||||
<title>osxphotos.phototemplate - 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">
|
||||
@@ -1350,7 +1350,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">"comparison operators may only be used with values that can be converted to numbers: </span><span class="si">{</span><span class="n">vals</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">conditional_value</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="sa">f</span><span class="s2">"comparison operators may only be used with values that can be converted to numbers: </span><span class="si">{</span><span class="n">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">"</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 +1879,7 @@
|
||||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Unhandled template value: </span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
|
||||
|
||||
|
||||
<span class="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">"PhotoInfo"</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">"PhotoInfo"</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">"""Get the value of a 'place' field by attribute</span>
|
||||
|
||||
<span class="sd"> Args:</span>
|
||||
|
||||
@@ -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.59.3'
|
||||
* - {osxphotos_cmd_line}
|
||||
- The full command line used to run osxphotos
|
||||
* - {album}
|
||||
|
||||
2
docs/_static/documentation_options.js
vendored
2
docs/_static/documentation_options.js
vendored
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.59.0',
|
||||
VERSION: '0.59.3',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Template System" href="template_help.html" /><link rel="prev" title="OSXPhotos Tutorial" href="tutorial.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-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.59.3 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=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.59.3 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.59.3 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -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.59.3 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=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.59.3 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.59.3 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -3305,10 +3305,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 +3320,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>
|
||||
|
||||
@@ -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.59.3 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=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.59.3 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.59.3 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -590,7 +590,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>
|
||||
|
||||
BIN
docs/objects.inv
BIN
docs/objects.inv
Binary file not shown.
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Tutorial" href="tutorial.html" /><link rel="prev" title="Welcome to OSXPhotos’s documentation!" href="index.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
|
||||
<title>OSXPhotos - osxphotos 0.59.0 documentation</title>
|
||||
<title>OSXPhotos - osxphotos 0.59.3 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=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.59.3 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.59.3 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -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.59.3 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=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.59.3 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.59.3 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -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.59.3 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=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.59.3 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.59.3 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="prev" title="OSXPhotos Python Package Overview" href="package_overview.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
|
||||
<title>OSXPhotos Python Reference - osxphotos 0.59.0 documentation</title>
|
||||
<title>OSXPhotos Python Reference - osxphotos 0.59.3 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=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.59.3 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.59.3 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -207,7 +207,7 @@ including folders, photos, etc.</p>
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.AlbumInfo.asdict">
|
||||
<span class="sig-name descname"><span class="pre">asdict</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/albuminfo.html#AlbumInfo.asdict"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.AlbumInfo.asdict" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Return album info as a dict</p>
|
||||
<dd><p>Return album info as a dict; does not include photos</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
@@ -1233,7 +1233,7 @@ including folders, albums, etc</p>
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.ImportInfo.asdict">
|
||||
<span class="sig-name descname"><span class="pre">asdict</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/albuminfo.html#ImportInfo.asdict"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.ImportInfo.asdict" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Return import info as a dict</p>
|
||||
<dd><p>Return import info as a dict; does not include photos</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
@@ -1490,8 +1490,20 @@ including keywords, persons, albums, uuid, path, etc.</p>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.asdict">
|
||||
<span class="sig-name descname"><span class="pre">asdict</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photoinfo.html#PhotoInfo.asdict"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotoInfo.asdict" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>return dict representation</p>
|
||||
<span class="sig-name descname"><span class="pre">asdict</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">shallow</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">bool</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">True</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><span class="pre">dict</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">Any</span><span class="p"><span class="pre">]</span></span></span></span><a class="reference internal" href="_modules/osxphotos/photoinfo.html#PhotoInfo.asdict"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotoInfo.asdict" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Return dict representation of PhotoInfo object.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p><strong>shallow</strong> – if True, return shallow representation (does not contain folder_info, person_info, etc.)</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-even"><p>dict representation of PhotoInfo object</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>The shallow representation is used internally by export as it contains only the subset of data needed for export.</p>
|
||||
</div>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
@@ -1814,8 +1826,23 @@ isMissing = 1</p>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.json">
|
||||
<span class="sig-name descname"><span class="pre">json</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photoinfo.html#PhotoInfo.json"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotoInfo.json" title="Permalink to this definition">#</a></dt>
|
||||
<span class="sig-name descname"><span class="pre">json</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">indent</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span><span class="w"> </span><span class="p"><span class="pre">|</span></span><span class="w"> </span><span class="pre">None</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">shallow</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">bool</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">True</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><span class="pre">str</span></span></span><a class="reference internal" href="_modules/osxphotos/photoinfo.html#PhotoInfo.json"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotoInfo.json" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Return JSON representation</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>indent</strong> – indent level for JSON, if None, no indent</p></li>
|
||||
<li><p><strong>shallow</strong> – if True, return shallow JSON representation (does not contain folder_info, person_info, etc.)</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-even"><p>JSON string</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>The shallow representation is used internally by export as it contains only the subset of data needed for export.</p>
|
||||
</div>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
@@ -2576,6 +2603,22 @@ Returns photos regardless of intrash state.</p>
|
||||
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">osxphotos.</span></span><span class="sig-name descname"><span class="pre">ProjectInfo</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">db</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">uuid</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/albuminfo.html#ProjectInfo"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.ProjectInfo" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>ProjectInfo with info about projects
|
||||
Projects are cards, calendars, slideshows, etc.</p>
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.ProjectInfo.folder_list">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">folder_list</span></span><a class="headerlink" href="#osxphotos.ProjectInfo.folder_list" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Returns list of FolderInfo objects for each folder the album is contained in
|
||||
or empty list if album is not in any folders</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.ProjectInfo.folder_names">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">folder_names</span></span><a class="headerlink" href="#osxphotos.ProjectInfo.folder_names" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>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</p>
|
||||
</dd></dl>
|
||||
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py class">
|
||||
@@ -3997,7 +4040,11 @@ Projects are cards, calendars, slideshows, etc.</p>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#osxphotos.PlaceInfo"><code class="docutils literal notranslate"><span class="pre">PlaceInfo</span></code></a></li>
|
||||
<li><a class="reference internal" href="#osxphotos.ProjectInfo"><code class="docutils literal notranslate"><span class="pre">ProjectInfo</span></code></a></li>
|
||||
<li><a class="reference internal" href="#osxphotos.ProjectInfo"><code class="docutils literal notranslate"><span class="pre">ProjectInfo</span></code></a><ul>
|
||||
<li><a class="reference internal" href="#osxphotos.ProjectInfo.folder_list"><code class="docutils literal notranslate"><span class="pre">ProjectInfo.folder_list</span></code></a></li>
|
||||
<li><a class="reference internal" href="#osxphotos.ProjectInfo.folder_names"><code class="docutils literal notranslate"><span class="pre">ProjectInfo.folder_names</span></code></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#osxphotos.QueryOptions"><code class="docutils literal notranslate"><span class="pre">QueryOptions</span></code></a><ul>
|
||||
<li><a class="reference internal" href="#osxphotos.QueryOptions.added_after"><code class="docutils literal notranslate"><span class="pre">QueryOptions.added_after</span></code></a></li>
|
||||
<li><a class="reference internal" href="#osxphotos.QueryOptions.added_before"><code class="docutils literal notranslate"><span class="pre">QueryOptions.added_before</span></code></a></li>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="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.59.3 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=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.59.3 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.59.3 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="#" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Python Package Overview" href="package_overview.html" /><link rel="prev" title="OSXPhotos Command Line Interface (CLI)" href="cli.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29"/>
|
||||
<title>OSXPhotos Template System - osxphotos 0.59.0 documentation</title>
|
||||
<title>OSXPhotos Template System - osxphotos 0.59.3 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=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.59.3 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.59.3 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -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.59.3’</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>
|
||||
|
||||
@@ -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.59.3 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=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.59.3 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.59.3 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -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.59.3'
|
||||
* - {osxphotos_cmd_line}
|
||||
- The full command line used to run osxphotos
|
||||
* - {album}
|
||||
|
||||
@@ -19,6 +19,7 @@ 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 .queryoptions import QueryOptions
|
||||
@@ -53,6 +54,7 @@ __all__ = [
|
||||
"PersonInfo",
|
||||
"PhotoExporter",
|
||||
"PhotoInfo",
|
||||
"PhotoTables",
|
||||
"PhotoTemplate",
|
||||
"PhotosAlbum",
|
||||
"PhotosAlbumPhotoScript",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.59.0"
|
||||
__version__ = "0.59.3"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
"""Common options & parameters for osxphotos CLI commands"""
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from typing import Any, Callable
|
||||
|
||||
import click
|
||||
|
||||
import contextlib
|
||||
from textwrap import dedent
|
||||
from .common import OSXPHOTOS_HIDDEN, print_version
|
||||
from .param_types import *
|
||||
|
||||
@@ -25,6 +26,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
|
||||
@@ -557,6 +601,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"],
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""export command for osxphotos CLI"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
import inspect
|
||||
import os
|
||||
@@ -11,8 +9,7 @@ import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from typing import Iterable, List, Optional, Tuple, Any, Callable
|
||||
import concurrent.futures
|
||||
from typing import Iterable, List, Optional, Tuple
|
||||
|
||||
import click
|
||||
from osxmetadata import (
|
||||
@@ -914,7 +911,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
|
||||
@@ -1429,156 +1427,174 @@ 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 = locals().copy()
|
||||
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
|
||||
limit_str = f" (limit = [num]{limit}[/num])" if limit else ""
|
||||
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
|
||||
)
|
||||
futures = []
|
||||
with concurrent.futures.ThreadPoolExecutor(
|
||||
# max_workers=os.cpu_count()
|
||||
max_workers=1,
|
||||
) as executor:
|
||||
for p in photos:
|
||||
photo_num += 1
|
||||
kwargs["photo_num"] = photo_num
|
||||
futures.append(executor.submit(export_worker, p, **kwargs))
|
||||
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
p, export_results = future.result()
|
||||
if album_export and export_results.exported:
|
||||
try:
|
||||
album_export.add(p)
|
||||
export_results.exported_album = [
|
||||
(filename, album_export.name)
|
||||
for filename in export_results.exported
|
||||
]
|
||||
except Exception as e:
|
||||
click.secho(
|
||||
f"Error adding photo {p.original_filename} ({p.uuid}) to album {album_export.name}: {e}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
err=True,
|
||||
)
|
||||
|
||||
if album_skipped and export_results.skipped:
|
||||
try:
|
||||
album_skipped.add(p)
|
||||
export_results.skipped_album = [
|
||||
(filename, album_skipped.name)
|
||||
for filename in export_results.skipped
|
||||
]
|
||||
except Exception as e:
|
||||
click.secho(
|
||||
f"Error adding photo {p.original_filename} ({p.uuid}) to album {album_skipped.name}: {e}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
err=True,
|
||||
)
|
||||
|
||||
if album_missing and export_results.missing:
|
||||
try:
|
||||
album_missing.add(p)
|
||||
export_results.missing_album = [
|
||||
(filename, album_missing.name)
|
||||
for filename in export_results.missing
|
||||
]
|
||||
except Exception as e:
|
||||
click.secho(
|
||||
f"Error adding photo {p.original_filename} ({p.uuid}) to album {album_missing.name}: {e}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
err=True,
|
||||
)
|
||||
|
||||
results += export_results
|
||||
|
||||
# all photo files (not including sidecars) that are part of this export set
|
||||
# used below for applying Finder tags, etc.
|
||||
photo_files = set(
|
||||
export_results.exported
|
||||
+ export_results.new
|
||||
+ export_results.updated
|
||||
+ export_results.exif_updated
|
||||
+ export_results.converted_to_jpeg
|
||||
+ export_results.skipped
|
||||
)
|
||||
|
||||
if finder_tag_keywords or finder_tag_template:
|
||||
if dry_run:
|
||||
for filepath in photo_files:
|
||||
verbose(
|
||||
f"Writing Finder tags to [filepath]{filepath}[/]"
|
||||
for p in photos:
|
||||
photo_num += 1
|
||||
kwargs["photo"] = p
|
||||
kwargs["photo_num"] = photo_num
|
||||
export_results = export_photo(**kwargs)
|
||||
if post_function:
|
||||
for function in post_function:
|
||||
# post function is tuple of (function, filename.py::function_name)
|
||||
verbose(f"Calling post-function [bold]{function[1]}")
|
||||
if not dry_run:
|
||||
try:
|
||||
function[0](p, export_results, verbose)
|
||||
except Exception as e:
|
||||
rich_echo_error(
|
||||
f"[error]Error running post-function [italic]{function[1]}[/italic]: {e}"
|
||||
)
|
||||
else:
|
||||
tags_written, tags_skipped = write_finder_tags(
|
||||
p,
|
||||
photo_files,
|
||||
keywords=finder_tag_keywords,
|
||||
keyword_template=keyword_template,
|
||||
album_keyword=album_keyword,
|
||||
person_keyword=person_keyword,
|
||||
exiftool_merge_keywords=exiftool_merge_keywords,
|
||||
finder_tag_template=finder_tag_template,
|
||||
strip=strip,
|
||||
export_dir=dest,
|
||||
verbose=verbose,
|
||||
|
||||
run_post_command(
|
||||
photo=p,
|
||||
post_command=post_command,
|
||||
export_results=export_results,
|
||||
export_dir=dest,
|
||||
dry_run=dry_run,
|
||||
exiftool_path=exiftool_path,
|
||||
export_db=export_db,
|
||||
verbose=verbose,
|
||||
)
|
||||
|
||||
if album_export and export_results.exported:
|
||||
try:
|
||||
album_export.add(p)
|
||||
export_results.exported_album = [
|
||||
(filename, album_export.name)
|
||||
for filename in export_results.exported
|
||||
]
|
||||
except Exception as e:
|
||||
click.secho(
|
||||
f"Error adding photo {p.original_filename} ({p.uuid}) to album {album_export.name}: {e}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
err=True,
|
||||
)
|
||||
|
||||
if album_skipped and export_results.skipped:
|
||||
try:
|
||||
album_skipped.add(p)
|
||||
export_results.skipped_album = [
|
||||
(filename, album_skipped.name)
|
||||
for filename in export_results.skipped
|
||||
]
|
||||
except Exception as e:
|
||||
click.secho(
|
||||
f"Error adding photo {p.original_filename} ({p.uuid}) to album {album_skipped.name}: {e}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
err=True,
|
||||
)
|
||||
|
||||
if album_missing and export_results.missing:
|
||||
try:
|
||||
album_missing.add(p)
|
||||
export_results.missing_album = [
|
||||
(filename, album_missing.name)
|
||||
for filename in export_results.missing
|
||||
]
|
||||
except Exception as e:
|
||||
click.secho(
|
||||
f"Error adding photo {p.original_filename} ({p.uuid}) to album {album_missing.name}: {e}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
err=True,
|
||||
)
|
||||
|
||||
results += export_results
|
||||
|
||||
# all photo files (not including sidecars) that are part of this export set
|
||||
# used below for applying Finder tags, etc.
|
||||
photo_files = set(
|
||||
export_results.exported
|
||||
+ export_results.new
|
||||
+ export_results.updated
|
||||
+ export_results.exif_updated
|
||||
+ export_results.converted_to_jpeg
|
||||
+ export_results.skipped
|
||||
)
|
||||
|
||||
if finder_tag_keywords or finder_tag_template:
|
||||
if dry_run:
|
||||
for filepath in photo_files:
|
||||
verbose(f"Writing Finder tags to [filepath]{filepath}[/]")
|
||||
else:
|
||||
tags_written, tags_skipped = write_finder_tags(
|
||||
p,
|
||||
photo_files,
|
||||
keywords=finder_tag_keywords,
|
||||
keyword_template=keyword_template,
|
||||
album_keyword=album_keyword,
|
||||
person_keyword=person_keyword,
|
||||
exiftool_merge_keywords=exiftool_merge_keywords,
|
||||
finder_tag_template=finder_tag_template,
|
||||
strip=strip,
|
||||
export_dir=dest,
|
||||
verbose=verbose,
|
||||
)
|
||||
export_results.xattr_written.extend(tags_written)
|
||||
export_results.xattr_skipped.extend(tags_skipped)
|
||||
results.xattr_written.extend(tags_written)
|
||||
results.xattr_skipped.extend(tags_skipped)
|
||||
|
||||
if xattr_template:
|
||||
if dry_run:
|
||||
for filepath in photo_files:
|
||||
verbose(
|
||||
f"Writing extended attributes to [filepath]{filepath}[/]"
|
||||
)
|
||||
export_results.xattr_written.extend(tags_written)
|
||||
export_results.xattr_skipped.extend(tags_skipped)
|
||||
results.xattr_written.extend(tags_written)
|
||||
results.xattr_skipped.extend(tags_skipped)
|
||||
else:
|
||||
xattr_written, xattr_skipped = write_extended_attributes(
|
||||
p,
|
||||
photo_files,
|
||||
xattr_template,
|
||||
strip=strip,
|
||||
export_dir=dest,
|
||||
verbose=verbose,
|
||||
)
|
||||
export_results.xattr_written.extend(xattr_written)
|
||||
export_results.xattr_skipped.extend(xattr_skipped)
|
||||
results.xattr_written.extend(xattr_written)
|
||||
results.xattr_skipped.extend(xattr_skipped)
|
||||
|
||||
if xattr_template:
|
||||
if dry_run:
|
||||
for filepath in photo_files:
|
||||
verbose(
|
||||
f"Writing extended attributes to [filepath]{filepath}[/]"
|
||||
)
|
||||
else:
|
||||
xattr_written, xattr_skipped = write_extended_attributes(
|
||||
p,
|
||||
photo_files,
|
||||
xattr_template,
|
||||
strip=strip,
|
||||
export_dir=dest,
|
||||
verbose=verbose,
|
||||
report_writer.write(export_results)
|
||||
|
||||
if print_template:
|
||||
options = RenderOptions(export_dir=dest)
|
||||
for template in print_template:
|
||||
rendered_templates, unmatched = p.render_template(
|
||||
template,
|
||||
options,
|
||||
)
|
||||
if unmatched:
|
||||
rich_click_echo(
|
||||
f"[warning]Unmatched template field: {unmatched}[/]"
|
||||
)
|
||||
export_results.xattr_written.extend(xattr_written)
|
||||
export_results.xattr_skipped.extend(xattr_skipped)
|
||||
results.xattr_written.extend(xattr_written)
|
||||
results.xattr_skipped.extend(xattr_skipped)
|
||||
for rendered_template in rendered_templates:
|
||||
if not rendered_template:
|
||||
continue
|
||||
rich_click_echo(rendered_template)
|
||||
|
||||
report_writer.write(export_results)
|
||||
progress.advance(task)
|
||||
|
||||
if print_template:
|
||||
options = RenderOptions(export_dir=dest)
|
||||
for template in print_template:
|
||||
rendered_templates, unmatched = p.render_template(
|
||||
template,
|
||||
options,
|
||||
)
|
||||
if unmatched:
|
||||
rich_click_echo(
|
||||
f"[warning]Unmatched template field: {unmatched}[/]"
|
||||
)
|
||||
for rendered_template in rendered_templates:
|
||||
if not rendered_template:
|
||||
continue
|
||||
rich_click_echo(rendered_template)
|
||||
|
||||
progress.advance(task)
|
||||
|
||||
# handle limit
|
||||
if export_results.exported:
|
||||
# if any photos were exported, increment num_exported used by limit
|
||||
# limit considers each PhotoInfo object as a single photo even if multiple files are exported
|
||||
num_exported += 1
|
||||
if limit and num_exported >= limit:
|
||||
# advance progress to end
|
||||
progress.advance(task, num_photos - photo_num)
|
||||
break
|
||||
# handle limit
|
||||
if export_results.exported:
|
||||
# if any photos were exported, increment num_exported used by limit
|
||||
# limit considers each PhotoInfo object as a single photo even if multiple files are exported
|
||||
num_exported += 1
|
||||
if limit and num_exported >= limit:
|
||||
# advance progress to end
|
||||
progress.advance(task, num_photos - photo_num)
|
||||
break
|
||||
|
||||
photo_str_total = pluralize(len(photos), "photo", "photos")
|
||||
if update or force_update:
|
||||
@@ -1668,45 +1684,6 @@ def export(
|
||||
export_db.close()
|
||||
|
||||
|
||||
def export_worker(
|
||||
photo: osxphotos.PhotoInfo, **kwargs
|
||||
) -> tuple[osxphotos.PhotoInfo, ExportResults]:
|
||||
"""Export worker function for multi-threaded export of photos"""
|
||||
dry_run = kwargs["dry_run"]
|
||||
verbose: Callable[[str], Any] = kwargs["verbose"]
|
||||
export_args = {
|
||||
k: v
|
||||
for k, v in kwargs.items()
|
||||
if k in inspect.getfullargspec(export_photo).args
|
||||
}
|
||||
export_args["photo"] = photo
|
||||
export_results = export_photo(**export_args)
|
||||
if post_function := kwargs["post_function"]:
|
||||
for function in post_function:
|
||||
# post function is tuple of (function, filename.py::function_name)
|
||||
verbose(f"Calling post-function [bold]{function[1]}")
|
||||
if not dry_run:
|
||||
try:
|
||||
function[0](photo, export_results, verbose)
|
||||
except Exception as e:
|
||||
rich_echo_error(
|
||||
f"[error]Error running post-function [italic]{function[1]}[/italic]: {e}"
|
||||
)
|
||||
|
||||
run_post_command(
|
||||
photo=photo,
|
||||
post_command=kwargs["post_command"],
|
||||
export_results=export_results,
|
||||
export_dir=kwargs["dest"],
|
||||
dry_run=dry_run,
|
||||
exiftool_path=kwargs["exiftool_path"],
|
||||
export_db=kwargs["export_db"],
|
||||
verbose=verbose,
|
||||
)
|
||||
|
||||
return photo, export_results
|
||||
|
||||
|
||||
def export_photo(
|
||||
photo=None,
|
||||
dest=None,
|
||||
@@ -2278,7 +2255,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(
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
"""repl command for osxphotos CLI"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
import click
|
||||
import photoscript
|
||||
from applescript import ScriptError
|
||||
from rich import pretty, print
|
||||
|
||||
import osxphotos
|
||||
@@ -191,10 +194,16 @@ 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])
|
||||
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: Can’t 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".*Can’t 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
|
||||
|
||||
|
||||
@@ -164,7 +164,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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
Binary file not shown.
@@ -1,11 +1,11 @@
|
||||
""" Yet another simple exiftool wrapper
|
||||
I rolled my own for following reasons:
|
||||
1. I wanted something under MIT license (best alternative was licensed under GPL/BSD)
|
||||
2. I wanted exiftool processes to stay resident between calls (improved performance)
|
||||
2. I wanted singleton behavior so only a single exiftool process was ever running
|
||||
3. When used as a context manager, I wanted the operations to batch until exiting the context (improved performance)
|
||||
"""
|
||||
If these aren't important to you, I highly recommend you use Sven Marnach's excellent
|
||||
pyexiftool: https://github.com/smarnach/pyexiftool which provides more functionality """
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
import contextlib
|
||||
@@ -17,7 +17,6 @@ import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
from abc import ABC, abstractmethod
|
||||
from functools import lru_cache # pylint: disable=syntax-error
|
||||
|
||||
@@ -31,8 +30,6 @@ __all__ = [
|
||||
"unescape_str",
|
||||
]
|
||||
|
||||
logger = logging.getLogger("osxphotos")
|
||||
|
||||
# exiftool -stay_open commands outputs this EOF marker after command is run
|
||||
EXIFTOOL_STAYOPEN_EOF = "{ready}"
|
||||
EXIFTOOL_STAYOPEN_EOF_LEN = len(EXIFTOOL_STAYOPEN_EOF)
|
||||
@@ -45,8 +42,6 @@ EXIFTOOL_FILETYPES_JSON = "exiftool_filetypes.json"
|
||||
with (pathlib.Path(__file__).parent / EXIFTOOL_FILETYPES_JSON).open("r") as f:
|
||||
EXIFTOOL_SUPPORTED_FILETYPES = json.load(f)
|
||||
|
||||
NUM_PROCESSES = os.cpu_count() or 1
|
||||
|
||||
|
||||
def exiftool_can_write(suffix: str) -> bool:
|
||||
"""Return True if exiftool supports writing to a file with the given suffix, otherwise False"""
|
||||
@@ -101,11 +96,8 @@ def get_exiftool_path():
|
||||
|
||||
|
||||
class _ExifToolProc:
|
||||
"""
|
||||
Runs exiftool in a subprocess via Popen
|
||||
Creates a singleton object that dispatches commands to one or
|
||||
more exiftool subprocesses.
|
||||
"""
|
||||
"""Runs exiftool in a subprocess via Popen
|
||||
Creates a singleton object"""
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""create new object or return instance of already created singleton"""
|
||||
@@ -114,11 +106,7 @@ class _ExifToolProc:
|
||||
|
||||
return cls.instance
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
exiftool: str | None = None,
|
||||
large_file_support: bool = True,
|
||||
):
|
||||
def __init__(self, exiftool=None, large_file_support=True):
|
||||
"""construct _ExifToolProc singleton object or return instance of already created object
|
||||
|
||||
Args:
|
||||
@@ -129,7 +117,7 @@ class _ExifToolProc:
|
||||
if hasattr(self, "_process_running") and self._process_running:
|
||||
# already running
|
||||
if exiftool is not None and exiftool != self._exiftool:
|
||||
logger.warning(
|
||||
logging.warning(
|
||||
f"exiftool subprocess already running, "
|
||||
f"ignoring exiftool={exiftool}"
|
||||
)
|
||||
@@ -137,9 +125,6 @@ class _ExifToolProc:
|
||||
self._process_running = False
|
||||
self._large_file_support = large_file_support
|
||||
self._exiftool = exiftool or get_exiftool_path()
|
||||
self._num_processes = NUM_PROCESSES
|
||||
self._process = []
|
||||
self._process_counter = 0
|
||||
self._start_proc(large_file_support=large_file_support)
|
||||
|
||||
@property
|
||||
@@ -147,20 +132,23 @@ class _ExifToolProc:
|
||||
"""return the exiftool subprocess"""
|
||||
if not self._process_running:
|
||||
self._start_proc(large_file_support=self._large_file_support)
|
||||
process_idx = self._process_counter % self._num_processes
|
||||
self._process_counter += 1
|
||||
return self._process[process_idx]
|
||||
return self._process
|
||||
|
||||
@property
|
||||
def pid(self):
|
||||
"""return process id (PID) of the exiftool process"""
|
||||
return self._process.pid
|
||||
|
||||
@property
|
||||
def exiftool(self):
|
||||
"""return path to exiftool process"""
|
||||
return self._exiftool
|
||||
|
||||
def _start_proc(self, large_file_support: bool):
|
||||
def _start_proc(self, large_file_support):
|
||||
"""start exiftool in batch mode"""
|
||||
|
||||
if self._process_running:
|
||||
logger.debug(f"exiftool already running: {self._process}")
|
||||
logging.warning("exiftool already running: {self._process}")
|
||||
return
|
||||
|
||||
# open exiftool process
|
||||
@@ -168,28 +156,25 @@ class _ExifToolProc:
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = f'/usr/bin/:{env["PATH"]}'
|
||||
large_file_args = ["-api", "largefilesupport=1"] if large_file_support else []
|
||||
for _ in range(self._num_processes):
|
||||
self._process.append(
|
||||
subprocess.Popen(
|
||||
[
|
||||
self._exiftool,
|
||||
"-stay_open", # keep process open in batch mode
|
||||
"True", # -stay_open=True, keep process open in batch mode
|
||||
*large_file_args,
|
||||
"-@", # read command-line arguments from file
|
||||
"-", # read from stdin
|
||||
"-common_args", # specifies args common to all commands subsequently run
|
||||
"-n", # no print conversion (e.g. print tag values in machine readable format)
|
||||
"-P", # Preserve file modification date/time
|
||||
"-G", # print group name for each tag
|
||||
"-E", # escape tag values for HTML (allows use of HTML 
 for newlines)
|
||||
],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
env=env,
|
||||
)
|
||||
)
|
||||
self._process = subprocess.Popen(
|
||||
[
|
||||
self._exiftool,
|
||||
"-stay_open", # keep process open in batch mode
|
||||
"True", # -stay_open=True, keep process open in batch mode
|
||||
*large_file_args,
|
||||
"-@", # read command-line arguments from file
|
||||
"-", # read from stdin
|
||||
"-common_args", # specifies args common to all commands subsequently run
|
||||
"-n", # no print conversion (e.g. print tag values in machine readable format)
|
||||
"-P", # Preserve file modification date/time
|
||||
"-G", # print group name for each tag
|
||||
"-E", # escape tag values for HTML (allows use of HTML 
 for newlines)
|
||||
],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
env=env,
|
||||
)
|
||||
self._process_running = True
|
||||
|
||||
EXIFTOOL_PROCESSES.append(self)
|
||||
@@ -200,19 +185,17 @@ class _ExifToolProc:
|
||||
if not self._process_running:
|
||||
return
|
||||
|
||||
for i in range(self._num_processes):
|
||||
process = self._process[i]
|
||||
with contextlib.suppress(Exception):
|
||||
process.stdin.write(b"-stay_open\n")
|
||||
process.stdin.write(b"False\n")
|
||||
process.stdin.flush()
|
||||
try:
|
||||
process.communicate(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
process.communicate()
|
||||
with contextlib.suppress(Exception):
|
||||
self._process.stdin.write(b"-stay_open\n")
|
||||
self._process.stdin.write(b"False\n")
|
||||
self._process.stdin.flush()
|
||||
try:
|
||||
self._process.communicate(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
self._process.kill()
|
||||
self._process.communicate()
|
||||
|
||||
self._process = []
|
||||
del self._process
|
||||
self._process_running = False
|
||||
|
||||
|
||||
@@ -250,7 +233,6 @@ class ExifTool:
|
||||
self._exiftoolproc = _ExifToolProc(
|
||||
exiftool=exiftool, large_file_support=large_file_support
|
||||
)
|
||||
self._lock = threading.Lock()
|
||||
self._read_exif()
|
||||
|
||||
@property
|
||||
@@ -354,54 +336,57 @@ class ExifTool:
|
||||
if not commands:
|
||||
raise TypeError("must provide one or more command to run")
|
||||
|
||||
with self._lock:
|
||||
if self._context_mgr and self.overwrite:
|
||||
commands = list(commands)
|
||||
commands.append("-overwrite_original")
|
||||
if self._context_mgr and self.overwrite:
|
||||
commands = list(commands)
|
||||
commands.append("-overwrite_original")
|
||||
|
||||
filename = b"" if no_file else os.fsencode(self.file)
|
||||
filename = b"" if no_file else os.fsencode(self.file)
|
||||
|
||||
if self.flags:
|
||||
# need to split flags, e.g. so "--ext AVI" becomes ["--ext", "AVI"]
|
||||
flags = []
|
||||
for f in self.flags:
|
||||
flags.extend(f.split())
|
||||
command_str = b"\n".join([f.encode("utf-8") for f in flags])
|
||||
command_str += b"\n"
|
||||
if self.flags:
|
||||
# need to split flags, e.g. so "--ext AVI" becomes ["--ext", "AVI"]
|
||||
flags = []
|
||||
for f in self.flags:
|
||||
flags.extend(f.split())
|
||||
command_str = b"\n".join([f.encode("utf-8") for f in flags])
|
||||
command_str += b"\n"
|
||||
else:
|
||||
command_str = b""
|
||||
|
||||
command_str += (
|
||||
b"\n".join([c.encode("utf-8") for c in commands])
|
||||
+ b"\n"
|
||||
+ filename
|
||||
+ b"\n"
|
||||
+ b"-execute\n"
|
||||
)
|
||||
|
||||
# send the command
|
||||
self._process.stdin.write(command_str)
|
||||
self._process.stdin.flush()
|
||||
|
||||
# read the output
|
||||
output = b""
|
||||
warning = b""
|
||||
error = b""
|
||||
while EXIFTOOL_STAYOPEN_EOF not in str(output):
|
||||
line = self._process.stdout.readline()
|
||||
if line.startswith(b"Warning"):
|
||||
warning += line.strip()
|
||||
elif line.startswith(b"Error"):
|
||||
error += line.strip()
|
||||
else:
|
||||
command_str = b""
|
||||
output += line.strip()
|
||||
warning = "" if warning == b"" else warning.decode("utf-8")
|
||||
error = "" if error == b"" else error.decode("utf-8")
|
||||
self.warning = warning
|
||||
self.error = error
|
||||
|
||||
command_str += (
|
||||
b"\n".join([c.encode("utf-8") for c in commands])
|
||||
+ b"\n"
|
||||
+ filename
|
||||
+ b"\n"
|
||||
+ b"-execute\n"
|
||||
)
|
||||
return output[:-EXIFTOOL_STAYOPEN_EOF_LEN], warning, error
|
||||
|
||||
# send the command
|
||||
process = self._process
|
||||
process.stdin.write(command_str)
|
||||
process.stdin.flush()
|
||||
|
||||
# read the output
|
||||
output = b""
|
||||
warning = b""
|
||||
error = b""
|
||||
while EXIFTOOL_STAYOPEN_EOF not in str(output):
|
||||
line = process.stdout.readline()
|
||||
if line.startswith(b"Warning"):
|
||||
warning += line.strip()
|
||||
elif line.startswith(b"Error"):
|
||||
error += line.strip()
|
||||
else:
|
||||
output += line.strip()
|
||||
warning = "" if warning == b"" else warning.decode("utf-8")
|
||||
error = "" if error == b"" else error.decode("utf-8")
|
||||
self.warning = warning
|
||||
self.error = error
|
||||
|
||||
return output[:-EXIFTOOL_STAYOPEN_EOF_LEN], warning, error
|
||||
@property
|
||||
def pid(self):
|
||||
"""return process id (PID) of the exiftool process"""
|
||||
return self._process.pid
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
@@ -419,7 +404,7 @@ class ExifTool:
|
||||
"""
|
||||
json_str, _, _ = self.run_commands("-json")
|
||||
if not json_str:
|
||||
return {}
|
||||
return dict()
|
||||
json_str = unescape_str(json_str.decode("utf-8"))
|
||||
|
||||
try:
|
||||
@@ -427,8 +412,8 @@ class ExifTool:
|
||||
except Exception as e:
|
||||
# will fail with some commands, e.g --ext AVI which produces
|
||||
# 'No file with specified extension' instead of json
|
||||
logger.warning(f"error loading json returned by exiftool: {e} {json_str}")
|
||||
return {}
|
||||
logging.warning(f"error loading json returned by exiftool: {e} {json_str}")
|
||||
return dict()
|
||||
exifdict = exifdict[0]
|
||||
if not tag_groups:
|
||||
# strip tag groups
|
||||
@@ -497,12 +482,7 @@ class _ExifToolCaching(ExifTool):
|
||||
"""
|
||||
self._json_cache = None
|
||||
self._asdict_cache = {}
|
||||
super().__init__(
|
||||
filepath,
|
||||
exiftool=exiftool,
|
||||
overwrite=False,
|
||||
flags=None,
|
||||
)
|
||||
super().__init__(filepath, exiftool=exiftool, overwrite=False, flags=None)
|
||||
|
||||
def run_commands(self, *commands, no_file=False):
|
||||
if commands[0] not in ["-json", "-ver"]:
|
||||
|
||||
@@ -1212,7 +1212,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 +1521,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)
|
||||
|
||||
@@ -59,6 +59,7 @@ 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 .query_builder import get_query
|
||||
@@ -517,39 +518,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 +554,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 +602,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):
|
||||
@@ -1777,41 +1740,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 +1784,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 +1793,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 +1808,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 +1834,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 +1892,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 +1910,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():
|
||||
|
||||
@@ -516,7 +516,8 @@ class PhotosDB:
|
||||
@property
|
||||
def album_info_shared(self):
|
||||
"""return list of AlbumInfo objects for each shared album in the photos database
|
||||
only valid for Photos 5; on Photos <= 4, prints warning and returns empty list"""
|
||||
only valid for Photos 5; on Photos <= 4, prints warning and returns empty list
|
||||
"""
|
||||
# if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album
|
||||
try:
|
||||
return self._album_info_shared
|
||||
@@ -543,7 +544,8 @@ class PhotosDB:
|
||||
@property
|
||||
def albums_shared(self):
|
||||
"""return list of shared albums found in photos database
|
||||
only valid for Photos 5; on Photos <= 4, prints warning and returns empty list"""
|
||||
only valid for Photos 5; on Photos <= 4, prints warning and returns empty list
|
||||
"""
|
||||
|
||||
# Could be more than one album with same name
|
||||
# Right now, they are treated as same album and photos are combined from albums with same name
|
||||
@@ -3053,7 +3055,6 @@ class PhotosDB:
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
# TODO: add to docs and test
|
||||
def photos_by_uuid(self, uuids):
|
||||
"""Returns a list of photos with UUID in uuids.
|
||||
Does not generate error if invalid or missing UUID passed.
|
||||
@@ -3066,14 +3067,11 @@ class PhotosDB:
|
||||
Returns:
|
||||
list of PhotoInfo instance for photo with UUID matching uuid or [] if no match
|
||||
"""
|
||||
photos = []
|
||||
for uuid in uuids:
|
||||
try:
|
||||
photos.append(PhotoInfo(db=self, uuid=uuid, info=self._dbphotos[uuid]))
|
||||
except KeyError:
|
||||
# ignore missing/invlaid UUID
|
||||
pass
|
||||
return photos
|
||||
return [
|
||||
PhotoInfo(db=self, uuid=uuid, info=self._dbphotos[uuid])
|
||||
for uuid in uuids
|
||||
if uuid in self._dbphotos
|
||||
]
|
||||
|
||||
def query(self, options: QueryOptions) -> List[PhotoInfo]:
|
||||
"""Run a query against PhotosDB to extract the photos based on user supplied options
|
||||
|
||||
230
osxphotos/phototables.py
Normal file
230
osxphotos/phototables.py
Normal file
@@ -0,0 +1,230 @@
|
||||
"""Provide direct access to the database tables associated with a photo."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from typing import Any
|
||||
|
||||
import osxphotos
|
||||
|
||||
from ._constants import _DB_TABLE_NAMES
|
||||
|
||||
|
||||
def get_table_columns(conn: sqlite3.Connection, table_name: str) -> list[str]:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"PRAGMA table_info({table_name})")
|
||||
return [col[1] for col in cursor.fetchall()]
|
||||
|
||||
|
||||
class PhotoTables:
|
||||
def __init__(self, photo: osxphotos.PhotoInfo):
|
||||
"""Create a PhotoTables object.
|
||||
|
||||
Args:
|
||||
db: PhotosDB object
|
||||
uuid: The UUID of the photo.
|
||||
"""
|
||||
self.db = photo._db
|
||||
self.photo = photo
|
||||
self.uuid = photo.uuid
|
||||
self.version = self.db._photos_ver
|
||||
|
||||
@property
|
||||
def ZASSET(self) -> Table:
|
||||
"""Return the ZASSET table."""
|
||||
return AssetTable(self.db, self.version, self.uuid)
|
||||
|
||||
@property
|
||||
def ZADDITIONALASSETATTRIBUTES(self) -> Table:
|
||||
"""Return the ZADDITIONALASSETATTRIBUTES table."""
|
||||
return AdditionalAttributesTable(self.db, self.version, self.uuid)
|
||||
|
||||
@property
|
||||
def ZDETECTEDFACE(self) -> Table:
|
||||
"""Return the ZDETECTEDFACE table."""
|
||||
return DetectedFaceTable(self.db, self.version, self.uuid)
|
||||
|
||||
@property
|
||||
def ZPERSON(self) -> Table:
|
||||
"""Return the ZPERSON table."""
|
||||
return PersonTable(self.db, self.version, self.uuid)
|
||||
|
||||
|
||||
class Table:
|
||||
def __init__(self, db: osxphotos.PhotosDB, version: int, uuid: str):
|
||||
"""Create a Table object.
|
||||
|
||||
Args:
|
||||
db: PhotosDB object
|
||||
table_name: The name of the table.
|
||||
"""
|
||||
self.db = db
|
||||
self.conn, _ = self.db.get_db_connection()
|
||||
self.version = version
|
||||
self.uuid = uuid
|
||||
self.asset_table = _DB_TABLE_NAMES[self.version]["ASSET"]
|
||||
self.columns = [] # must be set in subclass
|
||||
self.table_name = "" # must be set in subclass
|
||||
|
||||
def rows(self) -> list[tuple[Any]]:
|
||||
"""Return rows for this photo from the table."""
|
||||
# this should be implemented in the subclass
|
||||
raise NotImplementedError
|
||||
|
||||
def rows_dict(self) -> list[dict[str, Any]]:
|
||||
"""Return rows for this photo from the table as a list of dicts."""
|
||||
rows = self.rows()
|
||||
return [dict(zip(self.columns, row)) for row in rows] if rows else []
|
||||
|
||||
def _get_column(self, column: str):
|
||||
"""Get column value for this photo from the table."""
|
||||
# this should be implemented in the subclass
|
||||
raise NotImplementedError
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Get column value for this photo from the table."""
|
||||
if name not in self.__dict__ and name in self.columns:
|
||||
return self._get_column(name)
|
||||
else:
|
||||
raise AttributeError(f"Table {self.table_name} has no column {name}")
|
||||
|
||||
|
||||
class AssetTable(Table):
|
||||
"""ZASSET table."""
|
||||
|
||||
def __init__(self, db: osxphotos.PhotosDB, version: int, uuid: str):
|
||||
"""Create a Table object."""
|
||||
super().__init__(db, version, uuid)
|
||||
self.columns = get_table_columns(self.conn, self.asset_table)
|
||||
self.table_name = self.asset_table
|
||||
|
||||
def rows(self) -> list[Any]:
|
||||
"""Return row2 for this photo from the ZASSET table."""
|
||||
conn, cursor = self.db.get_db_connection()
|
||||
cursor.execute(
|
||||
f"SELECT * FROM {self.asset_table} WHERE ZUUID = ?", (self.uuid,)
|
||||
)
|
||||
return result if (result := cursor.fetchall()) else []
|
||||
|
||||
def _get_column(self, column: str) -> tuple[Any]:
|
||||
"""Get column value for this photo from the ZASSET table."""
|
||||
conn, cursor = self.db.get_db_connection()
|
||||
cursor.execute(
|
||||
f"SELECT {column} FROM {self.asset_table} WHERE ZUUID = ?",
|
||||
(self.uuid,),
|
||||
)
|
||||
return (
|
||||
tuple(result[0] for result in results)
|
||||
if (results := cursor.fetchall())
|
||||
else ()
|
||||
)
|
||||
|
||||
|
||||
class AdditionalAttributesTable(Table):
|
||||
"""ZADDITIONALASSETATTRIBUTES table."""
|
||||
|
||||
def __init__(self, db: osxphotos.PhotosDB, version: int, uuid: str):
|
||||
"""Create a Table object."""
|
||||
super().__init__(db, version, uuid)
|
||||
self.columns = get_table_columns(self.conn, "ZADDITIONALASSETATTRIBUTES")
|
||||
self.table_name = "ZADDITIONALASSETATTRIBUTES"
|
||||
|
||||
def rows(self) -> list[tuple[Any]]:
|
||||
"""Return rows for this photo from the ZADDITIONALASSETATTRIBUTES table."""
|
||||
conn, cursor = self.db.get_db_connection()
|
||||
sql = f""" SELECT ZADDITIONALASSETATTRIBUTES.*
|
||||
FROM ZADDITIONALASSETATTRIBUTES
|
||||
JOIN {self.asset_table} ON {self.asset_table}.Z_PK = ZADDITIONALASSETATTRIBUTES.ZASSET
|
||||
WHERE {self.asset_table}.ZUUID = ?;
|
||||
"""
|
||||
cursor.execute(sql, (self.uuid,))
|
||||
return result if (result := cursor.fetchall()) else []
|
||||
|
||||
def _get_column(self, column: str) -> tuple[Any]:
|
||||
"""Get column value for this photo from the ZADDITIONALASSETATTRIBUTES table."""
|
||||
conn, cursor = self.db.get_db_connection()
|
||||
sql = f""" SELECT ZADDITIONALASSETATTRIBUTES.{column}
|
||||
FROM ZADDITIONALASSETATTRIBUTES
|
||||
JOIN {self.asset_table} ON {self.asset_table}.Z_PK = ZADDITIONALASSETATTRIBUTES.ZASSET
|
||||
WHERE {self.asset_table}.ZUUID = ?;
|
||||
"""
|
||||
cursor.execute(sql, (self.uuid,))
|
||||
return (
|
||||
tuple(result[0] for result in results)
|
||||
if (results := cursor.fetchall())
|
||||
else ()
|
||||
)
|
||||
|
||||
|
||||
class DetectedFaceTable(Table):
|
||||
"""ZDETECTEDFACE table."""
|
||||
|
||||
def __init__(self, db: osxphotos.PhotosDB, version: int, uuid: str):
|
||||
"""Create a Table object."""
|
||||
super().__init__(db, version, uuid)
|
||||
self.columns = get_table_columns(self.conn, "ZDETECTEDFACE")
|
||||
self.table_name = "ZDETECTEDFACE"
|
||||
|
||||
def rows(self) -> list[tuple[Any]]:
|
||||
"""Return rows for this photo from the ZDETECTEDFACE table."""
|
||||
conn, cursor = self.db.get_db_connection()
|
||||
sql = f""" SELECT ZDETECTEDFACE.*
|
||||
FROM ZDETECTEDFACE
|
||||
JOIN {self.asset_table} ON {self.asset_table}.Z_PK = ZDETECTEDFACE.ZASSET
|
||||
WHERE {self.asset_table}.ZUUID = ?;
|
||||
"""
|
||||
cursor.execute(sql, (self.uuid,))
|
||||
return result if (result := cursor.fetchall()) else []
|
||||
|
||||
def _get_column(self, column: str) -> tuple[Any]:
|
||||
"""Get column value for this photo from the ZDETECTEDFACE table."""
|
||||
conn, cursor = self.db.get_db_connection()
|
||||
sql = f""" SELECT ZDETECTEDFACE.{column}
|
||||
FROM ZDETECTEDFACE
|
||||
JOIN {self.asset_table} ON {self.asset_table}.Z_PK = ZDETECTEDFACE.ZASSET
|
||||
WHERE {self.asset_table}.ZUUID = ?;
|
||||
"""
|
||||
cursor.execute(sql, (self.uuid,))
|
||||
return (
|
||||
tuple(result[0] for result in results)
|
||||
if (results := cursor.fetchall())
|
||||
else ()
|
||||
)
|
||||
|
||||
|
||||
class PersonTable(Table):
|
||||
"""ZPERSON table."""
|
||||
|
||||
def __init__(self, db: osxphotos.PhotosDB, version: int, uuid: str):
|
||||
"""Create a Table object."""
|
||||
super().__init__(db, version, uuid)
|
||||
self.columns = get_table_columns(self.conn, "ZPERSON")
|
||||
self.table_name = "ZPERSON"
|
||||
|
||||
def rows(self) -> list[tuple[Any]]:
|
||||
"""Return rows for this photo from the ZPERSON table."""
|
||||
conn, cursor = self.db.get_db_connection()
|
||||
sql = f""" SELECT ZPERSON.*
|
||||
FROM ZPERSON
|
||||
JOIN ZDETECTEDFACE ON ZDETECTEDFACE.ZPERSON = ZPERSON.Z_PK
|
||||
JOIN ZASSET ON ZASSET.Z_PK = ZDETECTEDFACE.ZASSET
|
||||
WHERE {self.asset_table}.ZUUID = ?;
|
||||
"""
|
||||
cursor.execute(sql, (self.uuid,))
|
||||
return result if (result := cursor.fetchall()) else []
|
||||
|
||||
def _get_column(self, column: str) -> tuple[Any]:
|
||||
"""Get column value for this photo from the ZPERSON table."""
|
||||
conn, cursor = self.db.get_db_connection()
|
||||
sql = f""" SELECT ZPERSON.{column}
|
||||
FROM ZPERSON
|
||||
JOIN ZDETECTEDFACE ON ZDETECTEDFACE.ZPERSON = ZPERSON.Z_PK
|
||||
JOIN ZASSET ON ZASSET.Z_PK = ZDETECTEDFACE.ZASSET
|
||||
WHERE {self.asset_table}.ZUUID = ?;
|
||||
"""
|
||||
cursor.execute(sql, (self.uuid,))
|
||||
return (
|
||||
tuple(result[0] for result in results)
|
||||
if (results := cursor.fetchall())
|
||||
else ()
|
||||
)
|
||||
@@ -1153,7 +1153,7 @@ class PhotoTemplate:
|
||||
)
|
||||
except ValueError as e:
|
||||
raise SyntaxError(
|
||||
f"comparison operators may only be used with values that can be converted to numbers: {vals} {conditional_value}"
|
||||
f"comparison operators may only be used with values that can be converted to numbers: {value} {conditional_value}"
|
||||
) from e
|
||||
|
||||
predicate_is_true = False
|
||||
@@ -1682,7 +1682,7 @@ def format_date_field(dt: datetime.datetime, field: str, args: List[str]) -> str
|
||||
raise ValueError(f"Unhandled template value: {field}") from e
|
||||
|
||||
|
||||
def get_place_value(photo: "PhotoInfo", field: str):
|
||||
def get_place_value(photo: "PhotoInfo", field: str): # noqa: F821
|
||||
"""Get the value of a 'place' field by attribute
|
||||
|
||||
Args:
|
||||
|
||||
@@ -432,7 +432,9 @@ def lock_filename(filepath: Union[str, pathlib.Path]) -> bool:
|
||||
Returns:
|
||||
filepath if lock file created, False if lock file already exists
|
||||
"""
|
||||
return filepath
|
||||
|
||||
# TODO: for future implementation
|
||||
lockfile = pathlib.Path(f"{filepath}.osxphotos.lock")
|
||||
if lockfile.exists():
|
||||
return False
|
||||
@@ -447,6 +449,9 @@ def unlock_filename(filepath: Union[str, pathlib.Path]):
|
||||
filepath: str or pathlib.Path; full path, including file name
|
||||
"""
|
||||
|
||||
return
|
||||
|
||||
# TODO: for future implementation
|
||||
lockfile = pathlib.Path(f"{filepath}.osxphotos.lock")
|
||||
if lockfile.exists():
|
||||
lockfile.unlink()
|
||||
@@ -539,6 +544,7 @@ def shortuuid_to_uuid(short_uuid: str) -> str:
|
||||
"""Convert shortuuid to uuid"""
|
||||
return str(shortuuid.decode(short_uuid)).upper()
|
||||
|
||||
|
||||
def under_test() -> bool:
|
||||
"""Return True if running under pytest"""
|
||||
return "pytest" in sys.modules
|
||||
return "pytest" in sys.modules
|
||||
|
||||
@@ -12,7 +12,7 @@ To set up a dev environment to work on osxphotos code or run tests follow these
|
||||
|
||||
To run the tests, do the following from the main source folder:
|
||||
`python3 -m pytest tests/`
|
||||
|
||||
- To run a specific test specify its name with the -k flag: `python3 -m pytest -k "test_export_cleanup"`
|
||||
|
||||
## Skipped Tests ##
|
||||
A few tests will look for certain environment variables to determine if they should run.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
""" Basic tests for Photos 5 on MacOS 10.15.7 """
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
@@ -341,19 +342,16 @@ def test_init5(mocker):
|
||||
|
||||
|
||||
def test_db_len(photosdb):
|
||||
|
||||
# assert photosdb.db_version in osxphotos._TESTED_DB_VERSIONS
|
||||
assert len(photosdb) == PHOTOS_DB_LEN
|
||||
|
||||
|
||||
def test_db_version(photosdb):
|
||||
|
||||
# assert photosdb.db_version in osxphotos._TESTED_DB_VERSIONS
|
||||
assert photosdb.db_version == "6000"
|
||||
|
||||
|
||||
def test_persons(photosdb):
|
||||
|
||||
assert "Katie" in photosdb.persons
|
||||
assert Counter(PERSONS) == Counter(photosdb.persons)
|
||||
|
||||
@@ -363,40 +361,34 @@ def test_photos_version(photosdb):
|
||||
|
||||
|
||||
def test_keywords(photosdb):
|
||||
|
||||
assert "wedding" in photosdb.keywords
|
||||
assert Counter(KEYWORDS) == Counter(photosdb.keywords)
|
||||
|
||||
|
||||
def test_album_names(photosdb):
|
||||
|
||||
assert "Pumpkin Farm" in photosdb.albums
|
||||
assert Counter(ALBUMS) == Counter(photosdb.albums)
|
||||
|
||||
|
||||
def test_keywords_dict(photosdb):
|
||||
|
||||
keywords = photosdb.keywords_as_dict
|
||||
assert keywords["wedding"] == 3
|
||||
assert keywords == KEYWORDS_DICT
|
||||
|
||||
|
||||
def test_persons_as_dict(photosdb):
|
||||
|
||||
persons = photosdb.persons_as_dict
|
||||
assert persons["Maria"] == 2
|
||||
assert persons == PERSONS_DICT
|
||||
|
||||
|
||||
def test_albums_as_dict(photosdb):
|
||||
|
||||
albums = photosdb.albums_as_dict
|
||||
assert albums["Pumpkin Farm"] == 3
|
||||
assert albums == ALBUM_DICT
|
||||
|
||||
|
||||
def test_album_sort_order(photosdb):
|
||||
|
||||
album = [a for a in photosdb.album_info if a.title == "Pumpkin Farm"][0]
|
||||
photos = album.photos
|
||||
|
||||
@@ -405,14 +397,12 @@ def test_album_sort_order(photosdb):
|
||||
|
||||
|
||||
def test_album_empty_album(photosdb):
|
||||
|
||||
album = [a for a in photosdb.album_info if a.title == "EmptyAlbum"][0]
|
||||
photos = album.photos
|
||||
assert photos == []
|
||||
|
||||
|
||||
def test_attributes(photosdb):
|
||||
|
||||
photos = photosdb.photos(uuid=["D79B8D77-BFFC-460B-9312-034F2877D35B"])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
@@ -484,7 +474,6 @@ def test_attributes_2(photosdb):
|
||||
|
||||
|
||||
def test_missing(photosdb):
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["missing"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
@@ -493,7 +482,6 @@ def test_missing(photosdb):
|
||||
|
||||
|
||||
def test_favorite(photosdb):
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["favorite"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
@@ -501,7 +489,6 @@ def test_favorite(photosdb):
|
||||
|
||||
|
||||
def test_not_favorite(photosdb):
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["not_favorite"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
@@ -509,7 +496,6 @@ def test_not_favorite(photosdb):
|
||||
|
||||
|
||||
def test_hidden(photosdb):
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["hidden"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
@@ -517,7 +503,6 @@ def test_hidden(photosdb):
|
||||
|
||||
|
||||
def test_not_hidden(photosdb):
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["not_hidden"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
@@ -656,7 +641,6 @@ def test_not_ismovie(photosdb):
|
||||
|
||||
|
||||
def test_count(photosdb):
|
||||
|
||||
photos = photosdb.photos()
|
||||
assert len(photos) == PHOTOS_NOT_IN_TRASH_LEN
|
||||
|
||||
@@ -735,13 +719,11 @@ def test_photoinfo_not_intrash(photosdb):
|
||||
|
||||
|
||||
def test_keyword_2(photosdb):
|
||||
|
||||
photos = photosdb.photos(keywords=["wedding"])
|
||||
assert len(photos) == 2 # won't show the one in the trash
|
||||
|
||||
|
||||
def test_keyword_not_in_album(photosdb):
|
||||
|
||||
# find all photos with keyword "Kids" not in the album "Pumpkin Farm"
|
||||
photos1 = photosdb.photos(albums=["Pumpkin Farm"])
|
||||
photos2 = photosdb.photos(keywords=["Kids"])
|
||||
@@ -758,20 +740,17 @@ def test_album_folder_name(photosdb):
|
||||
|
||||
|
||||
def test_multi_person(photosdb):
|
||||
|
||||
photos = photosdb.photos(persons=["Katie", "Suzy"])
|
||||
|
||||
assert len(photos) == 3
|
||||
|
||||
|
||||
def test_get_db_path(photosdb):
|
||||
|
||||
db_path = photosdb.db_path
|
||||
assert db_path.endswith(PHOTOS_DB_PATH)
|
||||
|
||||
|
||||
def test_get_library_path(photosdb):
|
||||
|
||||
lib_path = photosdb.library_path
|
||||
assert lib_path.endswith(PHOTOS_LIBRARY_PATH)
|
||||
|
||||
@@ -1080,14 +1059,12 @@ def test_eq_2():
|
||||
|
||||
|
||||
def test_not_eq(photosdb):
|
||||
|
||||
photos1 = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
photos2 = photosdb.photos(uuid=[UUID_DICT["missing"]])
|
||||
assert photos1[0] != photos2[0]
|
||||
|
||||
|
||||
def test_photosdb_repr():
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photosdb2 = eval(repr(photosdb))
|
||||
|
||||
@@ -1098,7 +1075,6 @@ def test_photosdb_repr():
|
||||
|
||||
|
||||
def test_photosinfo_repr(photosdb):
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["favorite"]])
|
||||
photo = photos[0]
|
||||
photo2 = eval(repr(photo))
|
||||
@@ -1502,3 +1478,33 @@ def test_fingerprint(photosdb):
|
||||
for uuid, fingerprint in UUID_FINGERPRINT.items():
|
||||
photo = photosdb.get_photo(uuid)
|
||||
assert photo.fingerprint == fingerprint
|
||||
|
||||
|
||||
def test_asdict(photosdb: osxphotos.PhotosDB):
|
||||
"""Test PhotoInfo.asdict()"""
|
||||
photo = photosdb.get_photo(UUID_DICT["favorite"])
|
||||
photo_dict = photo.asdict()
|
||||
assert photo_dict["favorite"]
|
||||
|
||||
|
||||
def test_json(photosdb: osxphotos.PhotosDB):
|
||||
"""Test PhotoInfo.json()"""
|
||||
photo = photosdb.get_photo(UUID_DICT["favorite"])
|
||||
photo_dict = json.loads(photo.json())
|
||||
assert photo_dict["favorite"]
|
||||
|
||||
|
||||
def test_json_indent(photosdb: osxphotos.PhotosDB):
|
||||
"""Test PhotoInfo.json() with indent"""
|
||||
photo = photosdb.get_photo(UUID_DICT["favorite"])
|
||||
photo_dict = json.loads(photo.json(indent=4, shallow=False))
|
||||
assert photo_dict["favorite"]
|
||||
assert "album_info" in photo_dict
|
||||
|
||||
|
||||
def test_json_shallow(photosdb: osxphotos.PhotosDB):
|
||||
"""Test PhotoInfo.json() with shallow=True"""
|
||||
photo = photosdb.get_photo(UUID_DICT["favorite"])
|
||||
photo_dict = json.loads(photo.json(shallow=True))
|
||||
assert photo_dict["favorite"]
|
||||
assert "album_info" not in photo_dict
|
||||
|
||||
@@ -12,8 +12,8 @@ UUID_MISSING = {
|
||||
}
|
||||
|
||||
# photos with matching names
|
||||
QUERY_NAME = "AAF035"
|
||||
QUERY_COUNT = 4
|
||||
QUERY_NAME = "IMG_"
|
||||
QUERY_COUNT = 6
|
||||
|
||||
|
||||
@pytest.mark.addalbum
|
||||
|
||||
23
tests/test_cli_export_projects.py
Normal file
23
tests/test_cli_export_projects.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""Test that libraries containing projects are handled correctly, #999"""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from osxphotos.cli import export
|
||||
|
||||
PHOTOS_DB_PROJECTS = "./tests/Test-iPhoto-Projects-10.15.7.photoslibrary"
|
||||
|
||||
|
||||
def test_export_projects():
|
||||
"""test basic export with library containing projects"""
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(
|
||||
export, ["--library", os.path.join(cwd, PHOTOS_DB_PROJECTS), ".", "-V"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "error: 0" in result.output
|
||||
@@ -419,6 +419,22 @@ def test_addvalues_unicode():
|
||||
assert sorted(exif.data["IPTC:Keywords"]) == sorted(["ǂ", "Ƕ"])
|
||||
|
||||
|
||||
def test_singleton():
|
||||
import osxphotos.exiftool
|
||||
|
||||
exif1 = osxphotos.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD)
|
||||
exif2 = osxphotos.exiftool.ExifTool(TEST_FILE_MULTI_KEYWORD)
|
||||
|
||||
assert exif1._process.pid == exif2._process.pid
|
||||
|
||||
|
||||
def test_pid():
|
||||
import osxphotos.exiftool
|
||||
|
||||
exif1 = osxphotos.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD)
|
||||
assert exif1.pid == exif1._process.pid
|
||||
|
||||
|
||||
def test_exiftoolproc_process():
|
||||
import osxphotos.exiftool
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from collections import namedtuple
|
||||
|
||||
import pytest
|
||||
|
||||
import osxphotos
|
||||
from osxphotos._constants import _UNKNOWN_PERSON
|
||||
|
||||
PHOTOS_DB = "./tests/Test-10.14.6.photoslibrary/database/photos.db"
|
||||
@@ -531,9 +532,10 @@ def test_photosdb_repr():
|
||||
|
||||
|
||||
def test_photosinfo_repr():
|
||||
import osxphotos
|
||||
import datetime # needed for eval to work
|
||||
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["favorite"]])
|
||||
photo = photos[0]
|
||||
@@ -689,3 +691,10 @@ def test_fingerprint(photosdb):
|
||||
for uuid, fingerprint in UUID_FINGERPRINT.items():
|
||||
photo = photosdb.get_photo(uuid)
|
||||
assert photo.fingerprint == fingerprint
|
||||
|
||||
|
||||
def test_tables(photosdb: osxphotos.PhotosDB):
|
||||
"""Test PhotoInfo.tables"""
|
||||
photo = photosdb.get_photo(UUID_DICT["favorite"])
|
||||
tables = photo.tables()
|
||||
assert tables is None
|
||||
|
||||
@@ -149,3 +149,11 @@ def test_photoinfo_project_info(photosdb, uuid, expected_projects):
|
||||
|
||||
project_names = [p.title for p in photo.project_info]
|
||||
assert sorted(project_names) == sorted(expected_projects)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("uuid,expected_projects", PHOTO_PROJECTS.items())
|
||||
def test_photoinfo_project_info_asdict(photosdb, uuid, expected_projects):
|
||||
"""Test PhotoInfo.project_info.asdict() #999"""
|
||||
photo = photosdb.get_photo(uuid)
|
||||
for p in photo.project_info:
|
||||
assert p.asdict()
|
||||
|
||||
@@ -27,7 +27,6 @@ def test_dd_to_dms():
|
||||
|
||||
@pytest.mark.skip(reason="Fails on some machines")
|
||||
def test_get_system_library_path():
|
||||
|
||||
_, major, _ = osxphotos.utils._get_os_version()
|
||||
if int(major) < 15:
|
||||
assert osxphotos.utils.get_system_library_path() is None
|
||||
@@ -84,7 +83,6 @@ def test_list_directory():
|
||||
|
||||
|
||||
def test_list_directory_invalid():
|
||||
|
||||
temp_dir = tempfile.TemporaryDirectory(prefix="osxphotos_")
|
||||
files = list_directory(f"{temp_dir.name}/no_such_dir", glob="*.jpg")
|
||||
assert len(files) == 0
|
||||
@@ -151,6 +149,7 @@ def test_increment_filename_with_lock():
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Lock files not yet implemented")
|
||||
def test_increment_filename_with_lock_exists():
|
||||
# test that increment_filename works with lock=True when lock file already exists
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Test macOS 13.0 Photos library"""
|
||||
|
||||
import json
|
||||
from collections import namedtuple
|
||||
|
||||
import pytest
|
||||
@@ -660,7 +661,6 @@ def test_keyword_2(photosdb):
|
||||
|
||||
|
||||
def test_keyword_not_in_album(photosdb):
|
||||
|
||||
# find all photos with keyword "Kids" not in the album "Pumpkin Farm"
|
||||
photos1 = photosdb.photos(albums=["Pumpkin Farm"])
|
||||
photos2 = photosdb.photos(keywords=["Kids"])
|
||||
@@ -1273,3 +1273,45 @@ def test_person_feature_less(photosdb):
|
||||
|
||||
photo = photosdb.get_photo(UUID_PERSON_NOT_FEATURE_LESS)
|
||||
assert not photo.person_info[0].feature_less
|
||||
|
||||
|
||||
def test_json(photosdb: osxphotos.PhotosDB):
|
||||
"""Test PhotoInfo.json()"""
|
||||
photo = photosdb.get_photo(UUID_DICT["favorite"])
|
||||
photo_dict = json.loads(photo.json())
|
||||
assert photo_dict["favorite"]
|
||||
|
||||
|
||||
def test_json_indent(photosdb: osxphotos.PhotosDB):
|
||||
"""Test PhotoInfo.json() with indent"""
|
||||
photo = photosdb.get_photo(UUID_DICT["favorite"])
|
||||
photo_dict = json.loads(photo.json(indent=4, shallow=False))
|
||||
assert photo_dict["favorite"]
|
||||
assert "album_info" in photo_dict
|
||||
|
||||
|
||||
def test_json_shallow(photosdb: osxphotos.PhotosDB):
|
||||
"""Test PhotoInfo.json() with shallow=True"""
|
||||
photo = photosdb.get_photo(UUID_DICT["favorite"])
|
||||
photo_dict = json.loads(photo.json(shallow=True))
|
||||
assert photo_dict["favorite"]
|
||||
assert "album_info" not in photo_dict
|
||||
|
||||
|
||||
def test_photosdb_photos_by_uuid(photosdb: osxphotos.PhotosDB):
|
||||
"""Test PhotosDB.photos_by_uuid"""
|
||||
photos = photosdb.photos_by_uuid(UUID_DICT.values())
|
||||
assert len(photos) == len(UUID_DICT)
|
||||
for photo in photos:
|
||||
assert photo.uuid in UUID_DICT.values()
|
||||
|
||||
|
||||
def test_tables(photosdb: osxphotos.PhotosDB):
|
||||
"""Test PhotoInfo.tables"""
|
||||
photo = photosdb.get_photo(UUID_DICT["in_album"])
|
||||
tables = photo.tables()
|
||||
assert isinstance(tables, osxphotos.PhotoTables)
|
||||
assert tables.ZASSET.ZUUID[0] == photo.uuid
|
||||
assert tables.ZADDITIONALASSETATTRIBUTES.ZTITLE[0] == photo.title
|
||||
assert len(tables.ZASSET.rows()) == 1
|
||||
assert tables.ZASSET.rows_dict()[0]["ZUUID"] == photo.uuid
|
||||
|
||||
Reference in New Issue
Block a user