Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68754273de | ||
|
|
d28a2fe9bb | ||
|
|
382d097285 | ||
|
|
93de53da51 | ||
|
|
e272e95a85 | ||
|
|
84a96bd4d0 | ||
|
|
d26b625d57 | ||
|
|
8731e7d5bc | ||
|
|
2e501e6a9b | ||
|
|
7f4c981abe | ||
|
|
bbcc3acba9 | ||
|
|
fccd746c58 | ||
|
|
adb90a3364 | ||
|
|
445010e7e5 | ||
|
|
1227465aa7 | ||
|
|
de1900f10a | ||
|
|
ed315fffd2 | ||
|
|
be1f3a98d9 | ||
|
|
d8802368fc | ||
|
|
f132e9a843 | ||
|
|
6b342a1733 | ||
|
|
9dec028448 | ||
|
|
8be6a98c32 | ||
|
|
ce73c9cab8 | ||
|
|
feb9538d1c | ||
|
|
b275280a1f | ||
|
|
924ef72446 | ||
|
|
c95f682ca6 | ||
|
|
d2753672f3 | ||
|
|
8ee4ea46c5 | ||
|
|
6fae979061 | ||
|
|
25d6f148be | ||
|
|
3704fc4a23 | ||
|
|
7883fc1911 | ||
|
|
29ff7f8666 | ||
|
|
43e1cb18cc | ||
|
|
26f916e4cb | ||
|
|
4e9e877b27 | ||
|
|
3a990e3997 | ||
|
|
4d1b1db2a7 | ||
|
|
173e3ccc37 | ||
|
|
9964fd0635 | ||
|
|
e789cd5e9d | ||
|
|
6cb7dedd9b | ||
|
|
39ba17dd1c | ||
|
|
5b66962ac1 | ||
|
|
c05340f631 | ||
|
|
f24c461cbb | ||
|
|
c8ee679799 | ||
|
|
2966c9a60f | ||
|
|
acfcb0c49a | ||
|
|
b92a681795 | ||
|
|
1941e79d21 | ||
|
|
5290fae2e0 | ||
|
|
ecbd370a47 | ||
|
|
d8204e65eb | ||
|
|
9c26e5519b | ||
|
|
060729c4c4 | ||
|
|
65d51ab129 | ||
|
|
afbda030bc | ||
|
|
d111d07fb7 | ||
|
|
30abdddaf3 | ||
|
|
a2f329b8de | ||
|
|
bfa888adc5 | ||
|
|
ac4083bfbb | ||
|
|
5fb686ac0c | ||
|
|
49a7b80680 | ||
|
|
cb11967eac | ||
|
|
a43bfc5a33 | ||
|
|
1d6bc4e09e | ||
|
|
3e14b718ef | ||
|
|
1ae6270561 | ||
|
|
55a601c07e | ||
|
|
7d67b81879 | ||
|
|
cd02144ac3 | ||
|
|
9b247acd1c | ||
|
|
942126ea3d | ||
|
|
2b9ea11701 | ||
|
|
b3d3e14ffe | ||
|
|
62ae5db9fd | ||
|
|
77a49a09a1 | ||
|
|
06c5bbfcfd | ||
|
|
f3063d35be | ||
|
|
e32090bf39 | ||
|
|
7ab500740b | ||
|
|
911bd30d28 | ||
|
|
282857eae0 | ||
|
|
d8c2f99c06 | ||
|
|
16d3f74366 | ||
|
|
5fc28139ea | ||
|
|
b7b6876688 |
@@ -257,7 +257,9 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/21261491?v=4",
|
||||
"profile": "https://github.com/oPromessa",
|
||||
"contributions": [
|
||||
"bug"
|
||||
"bug",
|
||||
"ideas",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
os: [macos-10.15]
|
||||
python-version: [3.7, 3.8, 3.9]
|
||||
python-version: ['3.8', '3.9', '3.10']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
@@ -32,4 +32,4 @@ jobs:
|
||||
# flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
python -m pytest tests/
|
||||
python -m pytest -v tests/
|
||||
|
||||
167
CHANGELOG.md
167
CHANGELOG.md
@@ -4,6 +4,173 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [v0.47.5](https://github.com/RhetTbull/osxphotos/compare/v0.47.4...v0.47.5)
|
||||
|
||||
> 12 March 2022
|
||||
|
||||
- Richify [`#653`](https://github.com/RhetTbull/osxphotos/pull/653)
|
||||
- Added --watch, --breakpoint [`#652`](https://github.com/RhetTbull/osxphotos/pull/652)
|
||||
- Hack to fix #654 when utime fails on NAS [`#654`](https://github.com/RhetTbull/osxphotos/issues/654)
|
||||
- Debug updates [`de1900f`](https://github.com/RhetTbull/osxphotos/commit/de1900f10aaac8d703ef5d850a64f18dd5e01d40)
|
||||
- Updated docs [skip ci] [`fccd746`](https://github.com/RhetTbull/osxphotos/commit/fccd746c581a319a8d1d5063133cbb9d5a4e1778)
|
||||
- Changed return val of _should_update_photo to enum for easier debugging [`bbcc3ac`](https://github.com/RhetTbull/osxphotos/commit/bbcc3acba9a4dd3a9ebcc8136782005d35e46255)
|
||||
- Updated crash_reporter to include crash data [`1227465`](https://github.com/RhetTbull/osxphotos/commit/1227465aa7e1d5a2fdaba4fc45cf917728d31170)
|
||||
- Fixed missing pdb.py issue for pyinstaller, partial for #659 [`e272e95`](https://github.com/RhetTbull/osxphotos/commit/e272e95a856e5b448eb6ac85818757b974dfd6d4)
|
||||
|
||||
#### [v0.47.4](https://github.com/RhetTbull/osxphotos/compare/v0.47.3...v0.47.4)
|
||||
|
||||
> 2 March 2022
|
||||
|
||||
- Added --tmpdir, #650 [`#651`](https://github.com/RhetTbull/osxphotos/pull/651)
|
||||
- Version bump [`6b342a1`](https://github.com/RhetTbull/osxphotos/commit/6b342a1733fa66d1663de2e3a234970f427a679f)
|
||||
- Version bump [`f132e9a`](https://github.com/RhetTbull/osxphotos/commit/f132e9a8438023e4c69c7fb767d24dea7465db4d)
|
||||
|
||||
#### [v0.47.3](https://github.com/RhetTbull/osxphotos/compare/v0.47.2...v0.47.3)
|
||||
|
||||
> 27 February 2022
|
||||
|
||||
- Help topic [`#644`](https://github.com/RhetTbull/osxphotos/pull/644)
|
||||
- updated docs [skip ci] [`ce73c9c`](https://github.com/RhetTbull/osxphotos/commit/ce73c9cab81fdd223dd49f2ff38608d553198412)
|
||||
- Added -v to pytest [`8be6a98`](https://github.com/RhetTbull/osxphotos/commit/8be6a98c3208b5da0fa620d1884a17275ab56599)
|
||||
|
||||
#### [v0.47.2](https://github.com/RhetTbull/osxphotos/compare/v0.47.1...v0.47.2)
|
||||
|
||||
> 27 February 2022
|
||||
|
||||
- Updated README.md [`b275280`](https://github.com/RhetTbull/osxphotos/commit/b275280a1f3e1b1a61dcc95aefebd4e326a47377)
|
||||
- Updated docs [skip ci] [`c95f682`](https://github.com/RhetTbull/osxphotos/commit/c95f682ca647f9b9e0718da658bdf7c735571f84)
|
||||
- Fix for --load-config, #643 [`feb9538`](https://github.com/RhetTbull/osxphotos/commit/feb9538d1c5ad6569232e5900393befb8ec1a57e)
|
||||
|
||||
#### [v0.47.1](https://github.com/RhetTbull/osxphotos/compare/v0.47.0...v0.47.1)
|
||||
|
||||
> 26 February 2022
|
||||
|
||||
- Fixed entry point [`d275367`](https://github.com/RhetTbull/osxphotos/commit/d2753672f36a0c84b7be143a47cc85cd6c99cb6d)
|
||||
|
||||
#### [v0.47.0](https://github.com/RhetTbull/osxphotos/compare/v0.46.6...v0.47.0)
|
||||
|
||||
> 26 February 2022
|
||||
|
||||
- CLI refactor [`#642`](https://github.com/RhetTbull/osxphotos/pull/642)
|
||||
- Updated docs [skip ci] [`6fae979`](https://github.com/RhetTbull/osxphotos/commit/6fae97906124c9284e382170e20c8ab9999105b0)
|
||||
- Fixed 3.10 in yaml [`3704fc4`](https://github.com/RhetTbull/osxphotos/commit/3704fc4a23e83ff2d16d6d221fb6c752dabcedca)
|
||||
- Dropped 3.7 [`7883fc1`](https://github.com/RhetTbull/osxphotos/commit/7883fc1911057df9a4c596375b498e85a73c1bec)
|
||||
|
||||
#### [v0.46.6](https://github.com/RhetTbull/osxphotos/compare/v0.46.5...v0.46.6)
|
||||
|
||||
> 26 February 2022
|
||||
|
||||
- Updated tests [`43e1cb1`](https://github.com/RhetTbull/osxphotos/commit/43e1cb18cc65b1abe1f49b464563e816b2ed1cff)
|
||||
- Updated docs [skip ci] [`3a990e3`](https://github.com/RhetTbull/osxphotos/commit/3a990e39971d838e52d5f19bf28b8253c4c7b811)
|
||||
- Bug fix for bitmath types in saved config [`26f916e`](https://github.com/RhetTbull/osxphotos/commit/26f916e4cbf4f28154c47aa2de1fdbc0aebc65b3)
|
||||
|
||||
#### [v0.46.5](https://github.com/RhetTbull/osxphotos/compare/v0.46.4...v0.46.5)
|
||||
|
||||
> 24 February 2022
|
||||
|
||||
- Updated tested versions [`4d1b1db`](https://github.com/RhetTbull/osxphotos/commit/4d1b1db2a7cf34afaa2dc5dbebc69021ff77964f)
|
||||
|
||||
#### [v0.46.4](https://github.com/RhetTbull/osxphotos/compare/v0.46.1...v0.46.4)
|
||||
|
||||
> 24 February 2022
|
||||
|
||||
- Removed debug code from exiftool, fixed #641 [`#641`](https://github.com/RhetTbull/osxphotos/issues/641)
|
||||
- Added debug output to exiftool [`39ba17d`](https://github.com/RhetTbull/osxphotos/commit/39ba17dd1cb4d8a61ab4dc8d5cff12ff9871eee0)
|
||||
- Fixed export of bursts with --uuid and --selected, #640 [`5b66962`](https://github.com/RhetTbull/osxphotos/commit/5b66962ac1bc1f48106fb8eeb600e6010088dc3b)
|
||||
- Added --sql command to exportdb [`c8ee679`](https://github.com/RhetTbull/osxphotos/commit/c8ee6797999af954c32e96ac3799a19002f4f0fe)
|
||||
- Updated docs [skip ci] [`2966c9a`](https://github.com/RhetTbull/osxphotos/commit/2966c9a60fc828afdf34263b759159a3ade31897)
|
||||
- Updated debug info [`6cb7ded`](https://github.com/RhetTbull/osxphotos/commit/6cb7dedd9be53d2c62489125fc44b9f4dccfb7ae)
|
||||
|
||||
#### [v0.46.1](https://github.com/RhetTbull/osxphotos/compare/v0.46.0...v0.46.1)
|
||||
|
||||
> 21 February 2022
|
||||
|
||||
- Added --ramdb option [`#639`](https://github.com/RhetTbull/osxphotos/pull/639)
|
||||
|
||||
#### [v0.46.0](https://github.com/RhetTbull/osxphotos/compare/v0.45.12...v0.46.0)
|
||||
|
||||
> 21 February 2022
|
||||
|
||||
- Exportdb refactor [`#638`](https://github.com/RhetTbull/osxphotos/pull/638)
|
||||
- Updated docs [skip ci] [`5290fae`](https://github.com/RhetTbull/osxphotos/commit/5290fae2e0ad062750348aedfee4feaf7b2e769f)
|
||||
|
||||
#### [v0.45.12](https://github.com/RhetTbull/osxphotos/compare/v0.45.11...v0.45.12)
|
||||
|
||||
> 14 February 2022
|
||||
|
||||
- Allow multiple characters as path_sep, #634 [`d8204e6`](https://github.com/RhetTbull/osxphotos/commit/d8204e65eb740cece468ef021cbdf45d896d954e)
|
||||
- Added --debug and crash reporter to export, #628 [`060729c`](https://github.com/RhetTbull/osxphotos/commit/060729c4c4255651c6ee8149989d9de541d0a6aa)
|
||||
- Added crash_reporter.py [`9c26e55`](https://github.com/RhetTbull/osxphotos/commit/9c26e5519b2d48f3a0ae80d1cc4a765c12b62d40)
|
||||
|
||||
#### [v0.45.11](https://github.com/RhetTbull/osxphotos/compare/v0.45.10...v0.45.11)
|
||||
|
||||
> 13 February 2022
|
||||
|
||||
- beta fix for #633, fix face regions in exiftool [`afbda03`](https://github.com/RhetTbull/osxphotos/commit/afbda030bce87f914445ebbced3f0e110e2e203b)
|
||||
- Updated docs [skip ci] [`65d51ab`](https://github.com/RhetTbull/osxphotos/commit/65d51ab1290e7c7804021e24829b93f5dce81245)
|
||||
|
||||
#### [v0.45.10](https://github.com/RhetTbull/osxphotos/compare/v0.45.9...v0.45.10)
|
||||
|
||||
> 12 February 2022
|
||||
|
||||
- Added --force-update, #621 [`30abddd`](https://github.com/RhetTbull/osxphotos/commit/30abdddaf3765f1d604984d4781b78b7806871e1)
|
||||
|
||||
#### [v0.45.9](https://github.com/RhetTbull/osxphotos/compare/v0.45.8...v0.45.9)
|
||||
|
||||
> 12 February 2022
|
||||
|
||||
- Added --force-update, #621 [`bfa888a`](https://github.com/RhetTbull/osxphotos/commit/bfa888adc5658a2845dcaa9b7ea360926ed4f000)
|
||||
- Refactored fix for #627 [`5fb686a`](https://github.com/RhetTbull/osxphotos/commit/5fb686ac0c231932c2695fc550a0824307bd3c5f)
|
||||
- Fix for #630 [`ac4083b`](https://github.com/RhetTbull/osxphotos/commit/ac4083bfbbabc8550718f0f7f8aadc635c05eb25)
|
||||
|
||||
#### [v0.45.8](https://github.com/RhetTbull/osxphotos/compare/v0.45.6...v0.45.8)
|
||||
|
||||
> 5 February 2022
|
||||
|
||||
- Fixed exiftool to ignore unsupported file types, #615 [`1ae6270`](https://github.com/RhetTbull/osxphotos/commit/1ae627056113fc4655f1b24cfbbdf0efc04489e7)
|
||||
- Updated tests [`55a601c`](https://github.com/RhetTbull/osxphotos/commit/55a601c07ea1384623c55d5c1d26b568df5d7823)
|
||||
- Additional fix for #615 [`1d6bc4e`](https://github.com/RhetTbull/osxphotos/commit/1d6bc4e09e3c2359a21f842fadd781920606812e)
|
||||
|
||||
#### [v0.45.6](https://github.com/RhetTbull/osxphotos/compare/v0.45.5...v0.45.6)
|
||||
|
||||
> 5 February 2022
|
||||
|
||||
- Fix for unicode in query strings, #618 [`9b247ac`](https://github.com/RhetTbull/osxphotos/commit/9b247acd1cc4b2def59fdd18a6fb3c8eb9914f11)
|
||||
- Fix for --name searching only original_filename on Photos 5+, #594 [`cd02144`](https://github.com/RhetTbull/osxphotos/commit/cd02144ac33cc1c13a20358133971c84d35b8a57)
|
||||
|
||||
#### [v0.45.5](https://github.com/RhetTbull/osxphotos/compare/v0.45.4...v0.45.5)
|
||||
|
||||
> 5 February 2022
|
||||
|
||||
- Fix for #561, no really, I mean it this time [`b3d3e14`](https://github.com/RhetTbull/osxphotos/commit/b3d3e14ffe41fbb22edb614b24f3985f379766a2)
|
||||
- Updated docs [skip ci] [`2b9ea11`](https://github.com/RhetTbull/osxphotos/commit/2b9ea11701799af9a661a8e2af70fca97235f487)
|
||||
- Updated tests for #561 [skip ci] [`77a49a0`](https://github.com/RhetTbull/osxphotos/commit/77a49a09a1bee74113a7114c543fbc25fa410ffc)
|
||||
|
||||
#### [v0.45.4](https://github.com/RhetTbull/osxphotos/compare/v0.45.3...v0.45.4)
|
||||
|
||||
> 3 February 2022
|
||||
|
||||
- docs: add oPromessa as a contributor for ideas, test [`#611`](https://github.com/RhetTbull/osxphotos/pull/611)
|
||||
- Fix for filenames with special characters, #561, #618 [`f3063d3`](https://github.com/RhetTbull/osxphotos/commit/f3063d35be3c96342d83dbd87ddd614a2001bff4)
|
||||
- Updated docs [skip ci] [`06c5bbf`](https://github.com/RhetTbull/osxphotos/commit/06c5bbfcfdf591a4a5d43f1456adaa27385fe01a)
|
||||
- Added progress counter, #601 [`7ab5007`](https://github.com/RhetTbull/osxphotos/commit/7ab500740b28594dcd778140e10991f839220e9d)
|
||||
- Updated known issues [skip ci] [`e32090b`](https://github.com/RhetTbull/osxphotos/commit/e32090bf39cb786171b49443f878ffdbab774420)
|
||||
|
||||
#### [v0.45.3](https://github.com/RhetTbull/osxphotos/compare/v0.45.2...v0.45.3)
|
||||
|
||||
> 29 January 2022
|
||||
|
||||
- Added --timestamp option for --verbose, #600 [`d8c2f99`](https://github.com/RhetTbull/osxphotos/commit/d8c2f99c06bc6f72bf2cb1a13c5765824fe3cbba)
|
||||
- Updated docs [skip ci] [`5fc2813`](https://github.com/RhetTbull/osxphotos/commit/5fc28139ea0374bc3e228c0432b8a41ada430389)
|
||||
- Updated formatting for elapsed time, #604 [`16d3f74`](https://github.com/RhetTbull/osxphotos/commit/16d3f743664396d43b3b3028a5e7a919ec56d9e1)
|
||||
|
||||
#### [v0.45.2](https://github.com/RhetTbull/osxphotos/compare/v0.45.0...v0.45.2)
|
||||
|
||||
> 29 January 2022
|
||||
|
||||
- Implemented #605, refactor out export2 [`235dea3`](https://github.com/RhetTbull/osxphotos/commit/235dea329c98ab8fa61565c09a1b4a83e5d99043)
|
||||
- Fix for #564, --preview with --download-missing [`5afdf6f`](https://github.com/RhetTbull/osxphotos/commit/5afdf6fc20a3cb6eb2b0217d8b3be20295eb7ba4)
|
||||
|
||||
#### [v0.45.0](https://github.com/RhetTbull/osxphotos/compare/v0.44.13...v0.45.0)
|
||||
|
||||
> 28 January 2022
|
||||
|
||||
12
MANIFEST.in
12
MANIFEST.in
@@ -1,7 +1,7 @@
|
||||
include README.md
|
||||
include README.rst
|
||||
include osxphotos/templates/*
|
||||
include osxphotos/*.json
|
||||
include osxphotos/*.md
|
||||
include osxphotos/phototemplate.tx
|
||||
include osxphotos/phototemplate.md
|
||||
include osxphotos/tutorial.md
|
||||
include osxphotos/queries/*
|
||||
include osxphotos/queries/*
|
||||
include osxphotos/templates/*
|
||||
include README.md
|
||||
include README.rst
|
||||
88
README.md
88
README.md
@@ -68,7 +68,7 @@ Only works on macOS (aka Mac OS X). Tested on macOS Sierra (10.12.6) through mac
|
||||
|
||||
This package will read Photos databases for any supported version on any supported macOS version. E.g. you can read a database created with Photos 5.0 on MacOS 10.15 on a machine running macOS 10.12 and vice versa.
|
||||
|
||||
Requires python >= `3.7`.
|
||||
Requires python >= `3.8`.
|
||||
|
||||
|
||||
## Installation
|
||||
@@ -162,7 +162,41 @@ Commands:
|
||||
uuid Print out unique IDs (UUID) of photos selected in Photos
|
||||
```
|
||||
|
||||
To get help on a specific command, use `osxphotos help <command_name>`
|
||||
To get help on a specific command, use `osxphotos help command_name`, for example, `osxphotos help export` to get help on the `export` command.
|
||||
|
||||
Some of the commands such as `export` and `query` have a large number of options. To search for options related to a specific topic, you can use `osxphotos help command_name topic_name`. For example, `osxphotos help export raw` finds the options related to RAW files (search is case-insensitive):
|
||||
|
||||
```
|
||||
Usage: osxphotos export [OPTIONS] [PHOTOS_LIBRARY]... DEST
|
||||
|
||||
Export photos from the Photos database. Export path DEST is required.
|
||||
Optionally, query the Photos database using 1 or more search options; if
|
||||
more than one option is provided, they are treated as "AND" (e.g. search for
|
||||
photos matching all options). If no query options are provided, all photos
|
||||
will be exported. By default, all versions of all photos will be exported
|
||||
including edited versions, live photo movies, burst photos, and associated
|
||||
raw images. See --skip-edited, --skip-live, --skip-bursts, and --skip-raw
|
||||
options to modify this behavior.
|
||||
|
||||
Options that match 'raw':
|
||||
|
||||
--has-raw Search for photos with both a jpeg and
|
||||
raw version
|
||||
--skip-raw Do not export associated RAW image of a
|
||||
RAW+JPEG pair. Note: this does not skip RAW
|
||||
photos if the RAW photo does not have an
|
||||
associated JPEG image (e.g. the RAW file was
|
||||
imported to Photos without a JPEG preview).
|
||||
--convert-to-jpeg Convert all non-JPEG images (e.g. RAW, HEIC,
|
||||
PNG, etc) to JPEG upon export. Note: does not
|
||||
convert the RAW component of a RAW+JPEG pair as
|
||||
the associated JPEG image will be exported. You
|
||||
can use --skip-raw to skip
|
||||
exporting the associated RAW image of a
|
||||
RAW+JPEG pair. See also --jpeg-quality and
|
||||
--jpeg-ext. Only works if your Mac has a GPU
|
||||
(thus may not work on virtual machines).
|
||||
```
|
||||
|
||||
### Command line examples
|
||||
|
||||
@@ -601,6 +635,8 @@ Options:
|
||||
library, 2. system library, 3.
|
||||
~/Pictures/Photos Library.photoslibrary
|
||||
-V, --verbose Print verbose output.
|
||||
--timestamp Add time stamp to verbose output
|
||||
--no-progress Do not display progress bar during export.
|
||||
--keyword KEYWORD Search for photos with keyword KEYWORD. If
|
||||
more than one keyword, treated as "OR", e.g.
|
||||
find photos matching any keyword
|
||||
@@ -782,8 +818,15 @@ Options:
|
||||
folder.
|
||||
--deleted-only Include only photos from the 'Recently
|
||||
Deleted' folder.
|
||||
--update Only export new or updated files. See notes
|
||||
below on export and --update.
|
||||
--update Only export new or updated files. See also
|
||||
--force-update and notes below on export and
|
||||
--update.
|
||||
--force-update Only export new or updated files. Unlike
|
||||
--update, --force-update will re-export photos
|
||||
if their metadata has changed even if this
|
||||
would not otherwise trigger an export. See
|
||||
also --update and notes below on export and
|
||||
--update.
|
||||
--ignore-signature When used with '--update', ignores file
|
||||
signature when updating files. This is useful
|
||||
if you have processed or edited exported
|
||||
@@ -1165,6 +1208,19 @@ Options:
|
||||
'.osxphotos_export.db' in the export
|
||||
directory. If --exportdb is specified, it
|
||||
will be saved to the specified file.
|
||||
--ramdb Copy export database to memory during export;
|
||||
may improve performance when exporting over a
|
||||
network or slow disk but could result in
|
||||
losing update state information if the program
|
||||
is interrupted or crashes.
|
||||
--tmpdir DIR Specify alternate temporary directory. Default
|
||||
is system temporary directory. osxphotos needs
|
||||
to create a number of temporary files during
|
||||
export. In some cases, particularly if the
|
||||
Photos library is on an APFS volume that is
|
||||
not the system volume, osxphotos may run
|
||||
faster if you specify a temporary directory on
|
||||
the same volume as the Photos library.
|
||||
--load-config <config file path>
|
||||
Load options from file as written with --save-
|
||||
config. This allows you to save a complex
|
||||
@@ -1178,8 +1234,16 @@ Options:
|
||||
corresponding values in the config file.
|
||||
--save-config <config file path>
|
||||
Save options to file for use with --load-
|
||||
config. File format is TOML.
|
||||
--help Show this message and exit.
|
||||
config. File format is TOML. See also
|
||||
--config-only.
|
||||
--config-only If specified, saves the config file but does
|
||||
not export any files; must be used with
|
||||
--save-config.
|
||||
--theme THEME Specify the color theme to use for --verbose
|
||||
output. Valid themes are 'dark', 'light',
|
||||
'mono', and 'plain'. Defaults to 'dark' or
|
||||
'light' depending on system dark mode setting.
|
||||
-h, --help Show this message and exit.
|
||||
|
||||
** Export **
|
||||
|
||||
@@ -1724,7 +1788,7 @@ Substitution Description
|
||||
{lf} A line feed: '\n', alias for {newline}
|
||||
{cr} A carriage return: '\r'
|
||||
{crlf} a carriage return + line feed: '\r\n'
|
||||
{osxphotos_version} The osxphotos version, e.g. '0.45.0'
|
||||
{osxphotos_version} The osxphotos version, e.g. '0.47.6'
|
||||
{osxphotos_cmd_line} The full command line used to run osxphotos
|
||||
|
||||
The following substitutions may result in multiple values. Thus if specified for
|
||||
@@ -3628,7 +3692,7 @@ The following template field substitutions are availabe for use the templating s
|
||||
|{lf}|A line feed: '\n', alias for {newline}|
|
||||
|{cr}|A carriage return: '\r'|
|
||||
|{crlf}|a carriage return + line feed: '\r\n'|
|
||||
|{osxphotos_version}|The osxphotos version, e.g. '0.45.0'|
|
||||
|{osxphotos_version}|The osxphotos version, e.g. '0.47.6'|
|
||||
|{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|
|
||||
@@ -3728,7 +3792,7 @@ Args:
|
||||
|
||||
Returns: ExportResults instance
|
||||
|
||||
*Note*: to use dry run mode, you must set options.dry_run=True and also pass in memory version of export_db, and no-op fileutil (e.g. ExportDBInMemory and FileUtilNoOp) in options.export_db and options.fileutil respectively.
|
||||
*Note*: to use dry run mode, you must set options.dry_run=True and also pass in memory version of export_db, and no-op fileutil (e.g. `ExportDBInMemory` and `FileUtilNoOp`) in options.export_db and options.fileutil respectively.
|
||||
|
||||
#### `ExportOptions`
|
||||
|
||||
@@ -3744,7 +3808,7 @@ Attributes:
|
||||
- exiftool_flags (list of str): optional list of flags to pass to exiftool when using exiftool option, e.g ["-m", "-F"]
|
||||
- exiftool: (bool, default = False): if True, will use exiftool to write metadata to export file
|
||||
- export_as_hardlink: (bool, default=False): if True, will hardlink files instead of copying them
|
||||
- export_db: (ExportDB_ABC): instance of a class that conforms to ExportDB_ABC with methods for getting/setting data related to exported files to compare update state
|
||||
- export_db: (ExportDB): instance of a class that conforms to ExportDB with methods for getting/setting data related to exported files to compare update state
|
||||
- fileutil: (FileUtilABC): class that conforms to FileUtilABC with various file utilities
|
||||
- ignore_date_modified (bool): for use with sidecar and exiftool; if True, sets EXIF:ModifyDate to EXIF:DateTimeOriginal even if date_modified is set
|
||||
- ignore_signature (bool, default=False): ignore file signature when used with update (look only at filename)
|
||||
@@ -3777,6 +3841,7 @@ Attributes:
|
||||
- use_photos_export (bool, default=False): if True will attempt to export photo via applescript interaction with Photos even if not missing (see also use_photokit, download_missing)
|
||||
- use_photokit (bool, default=False): if True, will use photokit to export photos when use_photos_export is True
|
||||
- verbose (Callable): optional callable function to use for printing verbose text during processing; if None (default), does not print output.
|
||||
- tmpfile (str): optional path to use for temporary files
|
||||
|
||||
#### `ExportResults`
|
||||
|
||||
@@ -3947,7 +4012,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<td align="center"><a href="https://github.com/mkirkland4874"><img src="https://avatars.githubusercontent.com/u/36466711?v=4?s=75" width="75px;" alt=""/><br /><sub><b>mkirkland4874</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Amkirkland4874" title="Bug reports">🐛</a> <a href="#example-mkirkland4874" title="Examples">💡</a></td>
|
||||
<td align="center"><a href="https://github.com/jcommisso07"><img src="https://avatars.githubusercontent.com/u/3111054?v=4?s=75" width="75px;" alt=""/><br /><sub><b>Joseph Commisso</b></sub></a><br /><a href="#data-jcommisso07" title="Data">🔣</a></td>
|
||||
<td align="center"><a href="https://github.com/dssinger"><img src="https://avatars.githubusercontent.com/u/1817903?v=4?s=75" width="75px;" alt=""/><br /><sub><b>David Singer</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Adssinger" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://github.com/oPromessa"><img src="https://avatars.githubusercontent.com/u/21261491?v=4?s=75" width="75px;" alt=""/><br /><sub><b>oPromessa</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3AoPromessa" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://github.com/oPromessa"><img src="https://avatars.githubusercontent.com/u/21261491?v=4?s=75" width="75px;" alt=""/><br /><sub><b>oPromessa</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3AoPromessa" title="Bug reports">🐛</a> <a href="#ideas-oPromessa" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/RhetTbull/osxphotos/commits?author=oPromessa" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="http://spencerchang.me"><img src="https://avatars.githubusercontent.com/u/14796580?v=4?s=75" width="75px;" alt=""/><br /><sub><b>Spencer Chang</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Aspencerc99" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -3973,7 +4038,6 @@ My goal is make osxphotos as reliable and comprehensive as possible. The test s
|
||||
|
||||
- Audio-only files are not handled. It is possible to store audio-only files in Photos. osxphotos currently only handles images and videos. See [Issue #436](https://github.com/RhetTbull/osxphotos/issues/436)
|
||||
- Face coordinates (mouth, left eye, right eye) may not be correct for images where the head is tilted. See [Issue #196](https://github.com/RhetTbull/osxphotos/issues/196).
|
||||
- Raw images imported to Photos with an associated jpeg preview are not handled correctly by osxphotos. osxphotos query and export will operate on the jpeg preview instead of the raw image as will `PhotoInfo.path`. If the user selects "Use RAW as original" in Photos, the raw image will be exported or operated on but the jpeg will be ignored. See [Issue #101](https://github.com/RhetTbull/osxphotos/issues/101). Note: Beta version of fix for this bug is implemented in the current version of osxphotos.
|
||||
- The `--download-missing` option for `osxphotos export` does not work correctly with burst images. It will download the primary image but not the other burst images. See [Issue #75](https://github.com/RhetTbull/osxphotos/issues/75).
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
@@ -23,7 +23,7 @@ If you have access to macOS 12 / Monterey beta and would like to help ensure osx
|
||||
This package will read Photos databases for any supported version on any supported macOS version.
|
||||
E.g. you can read a database created with Photos 5.0 on MacOS 10.15 on a machine running macOS 10.12 and vice versa.
|
||||
|
||||
Requires python >= ``3.7``.
|
||||
Requires python >= ``3.8``.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
4
cli.py
4
cli.py
@@ -12,7 +12,7 @@
|
||||
|
||||
"""
|
||||
|
||||
from osxphotos.cli import cli
|
||||
from osxphotos.cli.cli import cli_main
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
cli_main()
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
build
|
||||
m2r2
|
||||
pyinstaller==4.4
|
||||
pdbpp
|
||||
pyinstaller==4.10
|
||||
pytest-mock
|
||||
pytest==6.2.4
|
||||
pytest==7.0.1
|
||||
Sphinx
|
||||
sphinx_click
|
||||
sphinx_rtd_theme
|
||||
sphinxcontrib-programoutput
|
||||
twine
|
||||
wheel
|
||||
Sphinx
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: fb08da4139dc0e6a5dd26bd81551df02
|
||||
config: 6eb3624b24f85803db5736d9e23c1632
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Overview: module code — osxphotos 0.43.9 documentation</title>
|
||||
<title>Overview: module code — osxphotos 0.47.6 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../_static/alabaster.css" />
|
||||
<script data-url_root="../" id="documentation_options" src="../_static/documentation_options.js"></script>
|
||||
@@ -31,11 +31,7 @@
|
||||
<div class="body" role="main">
|
||||
|
||||
<h1>All modules for which code is available</h1>
|
||||
<ul><li><a href="osxphotos/photoinfo/_photoinfo_exifinfo.html">osxphotos.photoinfo._photoinfo_exifinfo</a></li>
|
||||
<li><a href="osxphotos/photoinfo/_photoinfo_export.html">osxphotos.photoinfo._photoinfo_export</a></li>
|
||||
<li><a href="osxphotos/photoinfo/_photoinfo_scoreinfo.html">osxphotos.photoinfo._photoinfo_scoreinfo</a></li>
|
||||
<li><a href="osxphotos/photoinfo/_photoinfo_searchinfo.html">osxphotos.photoinfo._photoinfo_searchinfo</a></li>
|
||||
<li><a href="osxphotos/photoinfo/photoinfo.html">osxphotos.photoinfo.photoinfo</a></li>
|
||||
<ul><li><a href="osxphotos/photoinfo.html">osxphotos.photoinfo</a></li>
|
||||
<li><a href="osxphotos/photosdb/photosdb.html">osxphotos.photosdb.photosdb</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -93,7 +89,7 @@
|
||||
©2021, Rhet Turnbull.
|
||||
|
||||
|
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.3.2</a>
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.4.0</a>
|
||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
||||
|
||||
</div>
|
||||
|
||||
1868
docs/_modules/osxphotos/photoinfo.html
Normal file
1868
docs/_modules/osxphotos/photoinfo.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos.photosdb.photosdb — osxphotos 0.43.8 documentation</title>
|
||||
<title>osxphotos.photosdb.photosdb — osxphotos 0.47.5 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../../../_static/alabaster.css" />
|
||||
<script data-url_root="../../../" id="documentation_options" src="../../../_static/documentation_options.js"></script>
|
||||
@@ -60,32 +60,39 @@
|
||||
<span class="n">_PHOTO_TYPE</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_3_VERSION</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_4_ALBUM_KIND</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_4_ALBUM_TYPE_ALBUM</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_4_ALBUM_TYPE_PROJECT</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_4_ALBUM_TYPE_SLIDESHOW</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_4_ROOT_FOLDER</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_4_TOP_LEVEL_ALBUM</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_4_TOP_LEVEL_ALBUMS</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_4_VERSION</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_5_ALBUM_KIND</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_5_FOLDER_KIND</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_5_IMPORT_SESSION_ALBUM_KIND</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_5_PROJECT_ALBUM_KIND</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_5_ROOT_FOLDER_KIND</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_5_SHARED_ALBUM_KIND</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_5_VERSION</span><span class="p">,</span>
|
||||
<span class="n">_TESTED_OS_VERSIONS</span><span class="p">,</span>
|
||||
<span class="n">_UNKNOWN_PERSON</span><span class="p">,</span>
|
||||
<span class="n">BURST_KEY</span><span class="p">,</span>
|
||||
<span class="n">BURST_PICK_TYPE_NONE</span><span class="p">,</span>
|
||||
<span class="n">BURST_SELECTED</span><span class="p">,</span>
|
||||
<span class="n">TIME_DELTA</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="kn">from</span> <span class="nn">.._version</span> <span class="kn">import</span> <span class="n">__version__</span>
|
||||
<span class="kn">from</span> <span class="nn">..albuminfo</span> <span class="kn">import</span> <span class="n">AlbumInfo</span><span class="p">,</span> <span class="n">FolderInfo</span><span class="p">,</span> <span class="n">ImportInfo</span>
|
||||
<span class="kn">from</span> <span class="nn">..albuminfo</span> <span class="kn">import</span> <span class="n">AlbumInfo</span><span class="p">,</span> <span class="n">FolderInfo</span><span class="p">,</span> <span class="n">ImportInfo</span><span class="p">,</span> <span class="n">ProjectInfo</span>
|
||||
<span class="kn">from</span> <span class="nn">..datetime_utils</span> <span class="kn">import</span> <span class="n">datetime_has_tz</span><span class="p">,</span> <span class="n">datetime_naive_to_local</span>
|
||||
<span class="kn">from</span> <span class="nn">..debug</span> <span class="kn">import</span> <span class="n">is_debug</span>
|
||||
<span class="kn">from</span> <span class="nn">..fileutil</span> <span class="kn">import</span> <span class="n">FileUtil</span>
|
||||
<span class="kn">from</span> <span class="nn">..personinfo</span> <span class="kn">import</span> <span class="n">PersonInfo</span>
|
||||
<span class="kn">from</span> <span class="nn">..photoinfo</span> <span class="kn">import</span> <span class="n">PhotoInfo</span>
|
||||
<span class="kn">from</span> <span class="nn">..phototemplate</span> <span class="kn">import</span> <span class="n">RenderOptions</span>
|
||||
<span class="kn">from</span> <span class="nn">..queryoptions</span> <span class="kn">import</span> <span class="n">QueryOptions</span>
|
||||
<span class="kn">from</span> <span class="nn">..rich_utils</span> <span class="kn">import</span> <span class="n">add_rich_markup_tag</span>
|
||||
<span class="kn">from</span> <span class="nn">..utils</span> <span class="kn">import</span> <span class="p">(</span>
|
||||
<span class="n">_check_file_exists</span><span class="p">,</span>
|
||||
<span class="n">_db_is_locked</span><span class="p">,</span>
|
||||
<span class="n">_debug</span><span class="p">,</span>
|
||||
<span class="n">_get_os_version</span><span class="p">,</span>
|
||||
<span class="n">_open_sql_file</span><span class="p">,</span>
|
||||
<span class="n">get_last_library_path</span><span class="p">,</span>
|
||||
@@ -94,6 +101,8 @@
|
||||
<span class="p">)</span>
|
||||
<span class="kn">from</span> <span class="nn">.photosdb_utils</span> <span class="kn">import</span> <span class="n">get_db_model_version</span><span class="p">,</span> <span class="n">get_db_version</span>
|
||||
|
||||
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"PhotosDB"</span><span class="p">]</span>
|
||||
|
||||
<span class="c1"># TODO: Add test for imageTimeZoneOffsetSeconds = None</span>
|
||||
<span class="c1"># TODO: Add test for __str__</span>
|
||||
<span class="c1"># TODO: Add special albums and magic albums</span>
|
||||
@@ -115,13 +124,14 @@
|
||||
<span class="n">labels_normalized_as_dict</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dbfile</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">verbose</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">exiftool</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dbfile</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">verbose</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">exiftool</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">rich</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="sd">"""Create a new PhotosDB object.</span>
|
||||
|
||||
<span class="sd"> Args:</span>
|
||||
<span class="sd"> dbfile: specify full path to photos library or photos.db; if None, will attempt to locate last library opened by Photos.</span>
|
||||
<span class="sd"> verbose: optional callable function to use for printing verbose text during processing; if None (default), does not print output.</span>
|
||||
<span class="sd"> exiftool: optional path to exiftool for methods that require this (e.g. PhotoInfo.exiftool); if not provided, will search PATH</span>
|
||||
<span class="sd"> rich: use rich with verbose output</span>
|
||||
|
||||
<span class="sd"> Raises:</span>
|
||||
<span class="sd"> FileNotFoundError if dbfile is not a valid Photos library.</span>
|
||||
@@ -144,6 +154,12 @@
|
||||
<span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="s2">"verbose must be callable"</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_verbose</span> <span class="o">=</span> <span class="n">verbose</span>
|
||||
|
||||
<span class="c1"># define functions for adding markup</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_filepath</span> <span class="o">=</span> <span class="n">add_rich_markup_tag</span><span class="p">(</span><span class="s2">"filepath"</span><span class="p">,</span> <span class="n">rich</span><span class="o">=</span><span class="n">rich</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_filename</span> <span class="o">=</span> <span class="n">add_rich_markup_tag</span><span class="p">(</span><span class="s2">"filename"</span><span class="p">,</span> <span class="n">rich</span><span class="o">=</span><span class="n">rich</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_uuid</span> <span class="o">=</span> <span class="n">add_rich_markup_tag</span><span class="p">(</span><span class="s2">"uuid"</span><span class="p">,</span> <span class="n">rich</span><span class="o">=</span><span class="n">rich</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_num</span> <span class="o">=</span> <span class="n">add_rich_markup_tag</span><span class="p">(</span><span class="s2">"num"</span><span class="p">,</span> <span class="n">rich</span><span class="o">=</span><span class="n">rich</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># enable beta features</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_beta</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
|
||||
@@ -289,7 +305,7 @@
|
||||
<span class="c1"># key is Z_PK of ZMOMENT table and values are the moment info</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_db_moment_pk</span> <span class="o">=</span> <span class="p">{}</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">is_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"dbfile = </span><span class="si">{</span><span class="n">dbfile</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">dbfile</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
@@ -306,7 +322,7 @@
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">_check_file_exists</span><span class="p">(</span><span class="n">dbfile</span><span class="p">):</span>
|
||||
<span class="k">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"dbfile </span><span class="si">{</span><span class="n">dbfile</span><span class="si">}</span><span class="s2"> does not exist"</span><span class="p">,</span> <span class="n">dbfile</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">is_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"dbfile = </span><span class="si">{</span><span class="n">dbfile</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># init database names</span>
|
||||
@@ -320,7 +336,7 @@
|
||||
<span class="c1"># or photosanalysisd</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbfile</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbfile_actual</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">dbfile</span><span class="p">)</span>
|
||||
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Processing database </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Processing database </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_filepath</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># if database is exclusively locked, make a copy of it and use the copy</span>
|
||||
<span class="c1"># Photos maintains an exclusive lock on the database file while Photos is open</span>
|
||||
@@ -340,13 +356,13 @@
|
||||
<span class="k">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"dbfile </span><span class="si">{</span><span class="n">dbfile</span><span class="si">}</span><span class="s2"> does not exist"</span><span class="p">,</span> <span class="n">dbfile</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbfile_actual</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span> <span class="o">=</span> <span class="n">dbfile</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Processing database </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile_actual</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Processing database </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_filepath</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile_actual</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="c1"># if database is exclusively locked, make a copy of it and use the copy</span>
|
||||
<span class="k">if</span> <span class="n">_db_is_locked</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile_actual</span><span class="p">):</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Database locked, creating temporary copy."</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_copy_db_file</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile_actual</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">is_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
|
||||
<span class="sa">f</span><span class="s2">"_dbfile = </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile</span><span class="si">}</span><span class="s2">, _dbfile_actual = </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile_actual</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="p">)</span>
|
||||
@@ -361,7 +377,7 @@
|
||||
<span class="n">masters_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">library_path</span><span class="p">,</span> <span class="s2">"originals"</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_masters_path</span> <span class="o">=</span> <span class="n">masters_path</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">is_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"library = </span><span class="si">{</span><span class="n">library_path</span><span class="si">}</span><span class="s2">, masters = </span><span class="si">{</span><span class="n">masters_path</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="nb">int</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_db_version</span><span class="p">)</span> <span class="o"><=</span> <span class="nb">int</span><span class="p">(</span><span class="n">_PHOTOS_4_VERSION</span><span class="p">):</span>
|
||||
@@ -462,7 +478,7 @@
|
||||
<span class="k">for</span> <span class="n">folder</span><span class="p">,</span> <span class="n">detail</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbfolder_details</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">detail</span><span class="p">[</span><span class="s2">"intrash"</span><span class="p">]</span>
|
||||
<span class="ow">and</span> <span class="ow">not</span> <span class="n">detail</span><span class="p">[</span><span class="s2">"isMagic"</span><span class="p">]</span>
|
||||
<span class="ow">and</span> <span class="n">detail</span><span class="p">[</span><span class="s2">"parentFolderUuid"</span><span class="p">]</span> <span class="o">==</span> <span class="n">_PHOTOS_4_TOP_LEVEL_ALBUM</span>
|
||||
<span class="ow">and</span> <span class="n">detail</span><span class="p">[</span><span class="s2">"parentFolderUuid"</span><span class="p">]</span> <span class="ow">in</span> <span class="n">_PHOTOS_4_TOP_LEVEL_ALBUMS</span>
|
||||
<span class="p">]</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">folders</span> <span class="o">=</span> <span class="p">[</span>
|
||||
@@ -483,7 +499,7 @@
|
||||
<span class="k">for</span> <span class="n">folder</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbfolder_details</span><span class="o">.</span><span class="n">values</span><span class="p">()</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">folder</span><span class="p">[</span><span class="s2">"intrash"</span><span class="p">]</span>
|
||||
<span class="ow">and</span> <span class="ow">not</span> <span class="n">folder</span><span class="p">[</span><span class="s2">"isMagic"</span><span class="p">]</span>
|
||||
<span class="ow">and</span> <span class="n">folder</span><span class="p">[</span><span class="s2">"parentFolderUuid"</span><span class="p">]</span> <span class="o">==</span> <span class="n">_PHOTOS_4_TOP_LEVEL_ALBUM</span>
|
||||
<span class="ow">and</span> <span class="n">folder</span><span class="p">[</span><span class="s2">"parentFolderUuid"</span><span class="p">]</span> <span class="ow">in</span> <span class="n">_PHOTOS_4_TOP_LEVEL_ALBUMS</span>
|
||||
<span class="p">]</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">folder_names</span> <span class="o">=</span> <span class="p">[</span>
|
||||
@@ -562,6 +578,18 @@
|
||||
<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="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">"""return list of AlbumInfo projects for each project in the database"""</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="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="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="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="p">]</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_project_info</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">db_version</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""return the database version as stored in LiGlobals table"""</span>
|
||||
@@ -605,7 +633,7 @@
|
||||
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Error copying</span><span class="si">{</span><span class="n">fname</span><span class="si">}</span><span class="s2"> to </span><span class="si">{</span><span class="n">dest_path</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span>
|
||||
<span class="k">raise</span> <span class="ne">Exception</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">is_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">dest_path</span><span class="p">)</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">dest_path</span>
|
||||
@@ -632,7 +660,7 @@
|
||||
<span class="c1"># print("Error linking " + fname + " to " + dest_path, file=sys.stderr)</span>
|
||||
<span class="c1"># raise Exception</span>
|
||||
|
||||
<span class="c1"># if _debug():</span>
|
||||
<span class="c1"># if is_debug():</span>
|
||||
<span class="c1"># logging.debug(dest_path)</span>
|
||||
|
||||
<span class="c1"># return dest_path</span>
|
||||
@@ -643,7 +671,7 @@
|
||||
|
||||
<span class="n">verbose</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_verbose</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="s2">"Processing database."</span><span class="p">)</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Database version: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_db_version</span><span class="si">}</span><span class="s2">."</span><span class="p">)</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Database version: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_num</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_db_version</span><span class="p">)</span><span class="si">}</span><span class="s2">."</span><span class="p">)</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_photos_ver</span> <span class="o">=</span> <span class="mi">4</span> <span class="c1"># only used in Photos 5+</span>
|
||||
|
||||
@@ -673,14 +701,18 @@
|
||||
|
||||
<span class="k">for</span> <span class="n">person</span> <span class="ow">in</span> <span class="n">c</span><span class="p">:</span>
|
||||
<span class="n">pk</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
|
||||
<span class="n">fullname</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="k">if</span> <span class="n">person</span><span class="p">[</span><span class="mi">2</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="n">_UNKNOWN_PERSON</span>
|
||||
<span class="n">fullname</span> <span class="o">=</span> <span class="p">(</span>
|
||||
<span class="n">normalize_unicode</span><span class="p">(</span><span class="n">person</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span>
|
||||
<span class="k">if</span> <span class="n">person</span><span class="p">[</span><span class="mi">2</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="n">_UNKNOWN_PERSON</span>
|
||||
<span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbpersons_pk</span><span class="p">[</span><span class="n">pk</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
|
||||
<span class="s2">"pk"</span><span class="p">:</span> <span class="n">pk</span><span class="p">,</span>
|
||||
<span class="s2">"uuid"</span><span class="p">:</span> <span class="n">person</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span>
|
||||
<span class="s2">"fullname"</span><span class="p">:</span> <span class="n">fullname</span><span class="p">,</span>
|
||||
<span class="s2">"facecount"</span><span class="p">:</span> <span class="n">person</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span>
|
||||
<span class="s2">"keyface"</span><span class="p">:</span> <span class="n">person</span><span class="p">[</span><span class="mi">5</span><span class="p">],</span>
|
||||
<span class="s2">"displayname"</span><span class="p">:</span> <span class="n">person</span><span class="p">[</span><span class="mi">4</span><span class="p">],</span>
|
||||
<span class="s2">"displayname"</span><span class="p">:</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">person</span><span class="p">[</span><span class="mi">4</span><span class="p">]),</span>
|
||||
<span class="s2">"photo_uuid"</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span>
|
||||
<span class="s2">"keyface_uuid"</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span>
|
||||
<span class="p">}</span>
|
||||
@@ -747,13 +779,6 @@
|
||||
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbfaces_pk</span><span class="p">[</span><span class="n">pk</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">uuid</span><span class="p">]</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Finished walking through persons"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbpersons_pk</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbpersons_fullname</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfaces_pk</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfaces_uuid</span><span class="p">))</span>
|
||||
|
||||
<span class="c1"># Get info on albums</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="s2">"Processing albums."</span><span class="p">)</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
@@ -881,24 +906,15 @@
|
||||
<span class="c1"># build folder hierarchy</span>
|
||||
<span class="k">for</span> <span class="n">album</span><span class="p">,</span> <span class="n">details</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_details</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
|
||||
<span class="n">parent_folder</span> <span class="o">=</span> <span class="n">details</span><span class="p">[</span><span class="s2">"folderUuid"</span><span class="p">]</span>
|
||||
<span class="k">if</span> <span class="n">details</span><span class="p">[</span>
|
||||
<span class="s2">"albumSubclass"</span>
|
||||
<span class="p">]</span> <span class="o">==</span> <span class="n">_PHOTOS_4_ALBUM_KIND</span> <span class="ow">and</span> <span class="n">parent_folder</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">[</span>
|
||||
<span class="n">_PHOTOS_4_TOP_LEVEL_ALBUM</span>
|
||||
<span class="p">]:</span>
|
||||
<span class="k">if</span> <span class="p">(</span>
|
||||
<span class="n">details</span><span class="p">[</span><span class="s2">"albumSubclass"</span><span class="p">]</span> <span class="o">==</span> <span class="n">_PHOTOS_4_ALBUM_KIND</span>
|
||||
<span class="ow">and</span> <span class="n">parent_folder</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">_PHOTOS_4_TOP_LEVEL_ALBUMS</span>
|
||||
<span class="p">):</span>
|
||||
<span class="n">folder_hierarchy</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_build_album_folder_hierarchy_4</span><span class="p">(</span><span class="n">parent_folder</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_folders</span><span class="p">[</span><span class="n">album</span><span class="p">]</span> <span class="o">=</span> <span class="n">folder_hierarchy</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_folders</span><span class="p">[</span><span class="n">album</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Finished walking through albums"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbums_album</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbums_uuid</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_details</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_folders</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfolder_details</span><span class="p">))</span>
|
||||
|
||||
<span class="c1"># Get info on keywords</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="s2">"Processing keywords."</span><span class="p">)</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
@@ -914,13 +930,16 @@
|
||||
<span class="sd"> RKMaster.uuid = RKVersion.masterUuid</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">for</span> <span class="n">keyword</span> <span class="ow">in</span> <span class="n">c</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">keyword</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_uuid</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_uuid</span><span class="p">[</span><span class="n">keyword</span><span class="p">[</span><span class="mi">1</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">keyword</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_keyword</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_keyword</span><span class="p">[</span><span class="n">keyword</span><span class="p">[</span><span class="mi">0</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">_dbkeywords_uuid</span><span class="p">[</span><span class="n">keyword</span><span class="p">[</span><span class="mi">1</span><span class="p">]]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">keyword</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_keyword</span><span class="p">[</span><span class="n">keyword</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">keyword</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
|
||||
<span class="k">for</span> <span class="n">keyword_title</span><span class="p">,</span> <span class="n">keyword_uuid</span><span class="p">,</span> <span class="n">_</span> <span class="ow">in</span> <span class="n">c</span><span class="p">:</span>
|
||||
<span class="n">keyword_title</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">keyword_title</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">_dbkeywords_uuid</span><span class="p">[</span><span class="n">keyword_uuid</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">keyword_title</span><span class="p">)</span>
|
||||
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_uuid</span><span class="p">[</span><span class="n">keyword_uuid</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">keyword_title</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">_dbkeywords_keyword</span><span class="p">[</span><span class="n">keyword_title</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">keyword_uuid</span><span class="p">)</span>
|
||||
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_keyword</span><span class="p">[</span><span class="n">keyword_title</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">keyword_uuid</span><span class="p">]</span>
|
||||
|
||||
<span class="c1"># Get info on disk volumes</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"select RKVolume.modelId, RKVolume.name from RKVolume"</span><span class="p">)</span>
|
||||
@@ -1042,13 +1061,11 @@
|
||||
|
||||
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">c</span><span class="p">:</span>
|
||||
<span class="n">uuid</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"uuid = '</span><span class="si">{</span><span class="n">uuid</span><span class="si">}</span><span class="s2">, master = '</span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"_uuid"</span><span class="p">]</span> <span class="o">=</span> <span class="n">uuid</span> <span class="c1"># stored here for easier debugging</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"modelID"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"masterUuid"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"filename"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"filename"</span><span class="p">]</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">3</span><span class="p">])</span>
|
||||
|
||||
<span class="c1"># There are sometimes negative values for lastmodifieddate in the database</span>
|
||||
<span class="c1"># I don't know what these mean but they will raise exception in datetime if</span>
|
||||
@@ -1103,7 +1120,7 @@
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"type"</span><span class="p">]</span> <span class="o">=</span> <span class="n">_MOVIE_TYPE</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="c1"># unknown</span>
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">is_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"WARNING: </span><span class="si">{</span><span class="n">uuid</span><span class="si">}</span><span class="s2"> found unknown type </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="mi">21</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"type"</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
@@ -1287,13 +1304,13 @@
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"volumeId"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"imagePath"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"isMissing"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"originalFilename"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"originalFilename"</span><span class="p">]</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">4</span><span class="p">])</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"UTI"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"modelID"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"fileSize"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">7</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"isTrulyRAW"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"alternateMasterUuid"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">9</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"filename"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">10</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"filename"</span><span class="p">]</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">10</span><span class="p">])</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos_master</span><span class="p">[</span><span class="n">uuid</span><span class="p">]</span> <span class="o">=</span> <span class="n">info</span>
|
||||
|
||||
<span class="c1"># get details needed to find path of the edited photos</span>
|
||||
@@ -1326,7 +1343,7 @@
|
||||
<span class="ow">and</span> <span class="n">row</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">==</span> <span class="mi">2</span>
|
||||
<span class="p">):</span>
|
||||
<span class="k">if</span> <span class="s2">"edit_resource_id"</span> <span class="ow">in</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">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">is_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
|
||||
<span class="sa">f</span><span class="s2">"WARNING: found more than one edit_resource_id for "</span>
|
||||
<span class="sa">f</span><span class="s2">"UUID </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s2">,adjustmentUUID </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="si">}</span><span class="s2">, modelID </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span>
|
||||
@@ -1565,39 +1582,6 @@
|
||||
|
||||
<span class="c1"># done processing, dump debug data if requested</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="s2">"Done processing details from Photos library."</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Faces (_dbfaces_uuid):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfaces_uuid</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Persons (_dbpersons_pk):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbpersons_pk</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Keywords by uuid (_dbkeywords_uuid):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_uuid</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Keywords by keyword (_dbkeywords_keywords):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_keyword</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Albums by uuid (_dbalbums_uuid):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbums_uuid</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Albums by album (_dbalbums_albums):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbums_album</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Album details (_dbalbum_details):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_details</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Album titles (_dbalbum_titles):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_titles</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Volumes (_dbvolumes):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbvolumes</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Photos (_dbphotos):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Burst Photos (dbphotos_burst:"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos_burst</span><span class="p">))</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">_build_album_folder_hierarchy_4</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="p">,</span> <span class="n">folders</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="sd">"""recursively build folder/album hierarchy</span>
|
||||
@@ -1615,7 +1599,7 @@
|
||||
<span class="k">if</span> <span class="n">parent_uuid</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">folders</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">parent_uuid</span> <span class="o">==</span> <span class="n">_PHOTOS_4_TOP_LEVEL_ALBUM</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">parent_uuid</span> <span class="ow">in</span> <span class="n">_PHOTOS_4_TOP_LEVEL_ALBUMS</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">folders</span><span class="p">:</span>
|
||||
<span class="c1"># this is a top-level folder with no sub-folders</span>
|
||||
<span class="n">folders</span> <span class="o">=</span> <span class="p">{</span><span class="n">uuid</span><span class="p">:</span> <span class="kc">None</span><span class="p">}</span>
|
||||
@@ -1638,7 +1622,7 @@
|
||||
<span class="sd"> but it works so don't touch it.</span>
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">is_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"_process_database5"</span><span class="p">)</span>
|
||||
<span class="n">verbose</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_verbose</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Processing database."</span><span class="p">)</span>
|
||||
@@ -1647,7 +1631,9 @@
|
||||
<span class="c1"># some of the tables/columns have different names in different versions of Photos</span>
|
||||
<span class="n">photos_ver</span> <span class="o">=</span> <span class="n">get_db_model_version</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_photos_ver</span> <span class="o">=</span> <span class="n">photos_ver</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Database version: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_db_version</span><span class="si">}</span><span class="s2">, </span><span class="si">{</span><span class="n">photos_ver</span><span class="si">}</span><span class="s2">."</span><span class="p">)</span>
|
||||
<span class="n">verbose</span><span class="p">(</span>
|
||||
<span class="sa">f</span><span class="s2">"Database version: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_num</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_db_version</span><span class="p">)</span><span class="si">}</span><span class="s2">, </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_num</span><span class="p">(</span><span class="n">photos_ver</span><span class="p">)</span><span class="si">}</span><span class="s2">."</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">asset_table</span> <span class="o">=</span> <span class="n">_DB_TABLE_NAMES</span><span class="p">[</span><span class="n">photos_ver</span><span class="p">][</span><span class="s2">"ASSET"</span><span class="p">]</span>
|
||||
<span class="n">keyword_join</span> <span class="o">=</span> <span class="n">_DB_TABLE_NAMES</span><span class="p">[</span><span class="n">photos_ver</span><span class="p">][</span><span class="s2">"KEYWORD_JOIN"</span><span class="p">]</span>
|
||||
<span class="n">asset_album_table</span> <span class="o">=</span> <span class="n">_DB_TABLE_NAMES</span><span class="p">[</span><span class="n">photos_ver</span><span class="p">][</span><span class="s2">"ASSET_ALBUM_TABLE"</span><span class="p">]</span>
|
||||
@@ -1660,7 +1646,7 @@
|
||||
<span class="n">hdr_type_column</span> <span class="o">=</span> <span class="n">_DB_TABLE_NAMES</span><span class="p">[</span><span class="n">photos_ver</span><span class="p">][</span><span class="s2">"HDR_TYPE"</span><span class="p">]</span>
|
||||
|
||||
<span class="c1"># Look for all combinations of persons and pictures</span>
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">is_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Getting information about persons"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># get info to associate persons with photos</span>
|
||||
@@ -1688,7 +1674,7 @@
|
||||
<span class="k">for</span> <span class="n">person</span> <span class="ow">in</span> <span class="n">c</span><span class="p">:</span>
|
||||
<span class="n">pk</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
|
||||
<span class="n">fullname</span> <span class="o">=</span> <span class="p">(</span>
|
||||
<span class="n">person</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
|
||||
<span class="n">normalize_unicode</span><span class="p">(</span><span class="n">person</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="n">person</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">!=</span> <span class="s2">""</span> <span class="ow">and</span> <span class="n">person</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
|
||||
<span class="k">else</span> <span class="n">_UNKNOWN_PERSON</span>
|
||||
<span class="p">)</span>
|
||||
@@ -1698,7 +1684,7 @@
|
||||
<span class="s2">"fullname"</span><span class="p">:</span> <span class="n">fullname</span><span class="p">,</span>
|
||||
<span class="s2">"facecount"</span><span class="p">:</span> <span class="n">person</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span>
|
||||
<span class="s2">"keyface"</span><span class="p">:</span> <span class="n">person</span><span class="p">[</span><span class="mi">4</span><span class="p">],</span>
|
||||
<span class="s2">"displayname"</span><span class="p">:</span> <span class="n">person</span><span class="p">[</span><span class="mi">5</span><span class="p">],</span>
|
||||
<span class="s2">"displayname"</span><span class="p">:</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">person</span><span class="p">[</span><span class="mi">5</span><span class="p">]),</span>
|
||||
<span class="s2">"photo_uuid"</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span>
|
||||
<span class="s2">"keyface_uuid"</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span>
|
||||
<span class="p">}</span>
|
||||
@@ -1762,13 +1748,6 @@
|
||||
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbfaces_pk</span><span class="p">[</span><span class="n">pk</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">uuid</span><span class="p">]</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Finished walking through persons"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbpersons_pk</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbpersons_fullname</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfaces_pk</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfaces_uuid</span><span class="p">))</span>
|
||||
|
||||
<span class="c1"># get details about albums</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="s2">"Processing albums."</span><span class="p">)</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
@@ -1885,13 +1864,6 @@
|
||||
<span class="c1"># shared albums can't be in folders</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_folders</span><span class="p">[</span><span class="n">album</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Finished walking through albums"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbums_album</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbums_uuid</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_details</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_folders</span><span class="p">))</span>
|
||||
|
||||
<span class="c1"># get details on keywords</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="s2">"Processing keywords."</span><span class="p">)</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
@@ -1901,29 +1873,22 @@
|
||||
<span class="s2"> JOIN Z_1KEYWORDS ON Z_1KEYWORDS.Z_1ASSETATTRIBUTES = ZADDITIONALASSETATTRIBUTES.Z_PK </span>
|
||||
<span class="s2"> JOIN ZKEYWORD ON ZKEYWORD.Z_PK = </span><span class="si">{</span><span class="n">keyword_join</span><span class="si">}</span><span class="s2"> """</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">for</span> <span class="n">keyword</span> <span class="ow">in</span> <span class="n">c</span><span class="p">:</span>
|
||||
<span class="n">keyword_title</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">keyword</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">keyword</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_uuid</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_uuid</span><span class="p">[</span><span class="n">keyword</span><span class="p">[</span><span class="mi">1</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">keyword_title</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_keyword</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_keyword</span><span class="p">[</span><span class="n">keyword_title</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">_dbkeywords_uuid</span><span class="p">[</span><span class="n">keyword</span><span class="p">[</span><span class="mi">1</span><span class="p">]]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">keyword</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_keyword</span><span class="p">[</span><span class="n">keyword_title</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">keyword</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Finished walking through keywords"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_keyword</span><span class="p">))</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_uuid</span><span class="p">))</span>
|
||||
<span class="k">for</span> <span class="n">keyword_title</span><span class="p">,</span> <span class="n">keyword_uuid</span> <span class="ow">in</span> <span class="n">c</span><span class="p">:</span>
|
||||
<span class="n">keyword_title</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">keyword_title</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">_dbkeywords_uuid</span><span class="p">[</span><span class="n">keyword_uuid</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">keyword_title</span><span class="p">)</span>
|
||||
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_uuid</span><span class="p">[</span><span class="n">keyword_uuid</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">keyword_title</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">_dbkeywords_keyword</span><span class="p">[</span><span class="n">keyword_title</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">keyword_uuid</span><span class="p">)</span>
|
||||
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_keyword</span><span class="p">[</span><span class="n">keyword_title</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">keyword_uuid</span><span class="p">]</span>
|
||||
|
||||
<span class="c1"># get details on disk volumes</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"SELECT ZUUID, ZNAME from ZFILESYSTEMVOLUME"</span><span class="p">)</span>
|
||||
<span class="k">for</span> <span class="n">vol</span> <span class="ow">in</span> <span class="n">c</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbvolumes</span><span class="p">[</span><span class="n">vol</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span> <span class="o">=</span> <span class="n">vol</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Finished walking through volumes"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbvolumes</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># get details about photos</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="s2">"Processing photo details."</span><span class="p">)</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
@@ -2057,8 +2022,8 @@
|
||||
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"hidden"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">9</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"favorite"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">10</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"originalFilename"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"filename"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">12</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"originalFilename"</span><span class="p">]</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">3</span><span class="p">])</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"filename"</span><span class="p">]</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">12</span><span class="p">])</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"directory"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">11</span><span class="p">]</span>
|
||||
|
||||
<span class="c1"># set latitude and longitude</span>
|
||||
@@ -2090,7 +2055,7 @@
|
||||
<span class="k">elif</span> <span class="n">row</span><span class="p">[</span><span class="mi">17</span><span class="p">]</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"type"</span><span class="p">]</span> <span class="o">=</span> <span class="n">_MOVIE_TYPE</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">is_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"WARNING: </span><span class="si">{</span><span class="n">uuid</span><span class="si">}</span><span class="s2"> found unknown type </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="mi">17</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"type"</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
@@ -2289,7 +2254,7 @@
|
||||
<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>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"extendedDescription"</span><span class="p">]</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">is_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
|
||||
<span class="sa">f</span><span class="s2">"WARNING: found description </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="si">}</span><span class="s2"> but no photo for </span><span class="si">{</span><span class="n">uuid</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="p">)</span>
|
||||
@@ -2308,7 +2273,7 @@
|
||||
<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>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"adjustmentFormatID"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">is_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
|
||||
<span class="sa">f</span><span class="s2">"WARNING: found adjustmentformatidentifier </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="si">}</span><span class="s2"> but no photo for uuid </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="p">)</span>
|
||||
@@ -2534,50 +2499,7 @@
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="s2">"Processing moments."</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_process_moments</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># done processing, dump debug data if requested</span>
|
||||
<span class="n">verbose</span><span class="p">(</span><span class="s2">"Done processing details from Photos library."</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">():</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Faces (_dbfaces_uuid):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfaces_uuid</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Persons (_dbpersons_pk):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbpersons_pk</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Keywords by uuid (_dbkeywords_uuid):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_uuid</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Keywords by keyword (_dbkeywords_keywords):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_keyword</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Albums by uuid (_dbalbums_uuid):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbums_uuid</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Albums by album (_dbalbums_albums):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbums_album</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Album details (_dbalbum_details):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_details</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Album titles (_dbalbum_titles):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_titles</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Album folders (_dbalbum_folders):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_folders</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Album parent folders (_dbalbum_parent_folders):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_parent_folders</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Albums pk (_dbalbums_pk):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbalbums_pk</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Volumes (_dbvolumes):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbvolumes</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Photos (_dbphotos):"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">))</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">"Burst Photos (dbphotos_burst:"</span><span class="p">)</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">pformat</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos_burst</span><span class="p">))</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">_process_moments</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Process data from ZMOMENT table"""</span>
|
||||
@@ -2638,8 +2560,8 @@
|
||||
<span class="n">moment_info</span><span class="p">[</span><span class="s2">"modificationDate"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span>
|
||||
<span class="n">moment_info</span><span class="p">[</span><span class="s2">"representativeDate"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">7</span><span class="p">]</span>
|
||||
<span class="n">moment_info</span><span class="p">[</span><span class="s2">"startDate"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span>
|
||||
<span class="n">moment_info</span><span class="p">[</span><span class="s2">"subtitle"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">9</span><span class="p">]</span>
|
||||
<span class="n">moment_info</span><span class="p">[</span><span class="s2">"title"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">10</span><span class="p">]</span>
|
||||
<span class="n">moment_info</span><span class="p">[</span><span class="s2">"subtitle"</span><span class="p">]</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">9</span><span class="p">])</span>
|
||||
<span class="n">moment_info</span><span class="p">[</span><span class="s2">"title"</span><span class="p">]</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">10</span><span class="p">])</span>
|
||||
<span class="n">moment_info</span><span class="p">[</span><span class="s2">"uuid"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">11</span><span class="p">]</span>
|
||||
|
||||
<span class="c1"># if both lat/lon == -180, then it means location undefined</span>
|
||||
@@ -2858,7 +2780,7 @@
|
||||
<span class="n">hierarchy</span> <span class="o">=</span> <span class="n">_recurse_folder_hierarchy</span><span class="p">(</span><span class="n">folders</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">hierarchy</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">_get_album_uuids</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">shared</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">import_session</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
|
||||
<span class="k">def</span> <span class="nf">_get_album_uuids</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">shared</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">import_session</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">project</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
|
||||
<span class="sd">"""Return list of album UUIDs found in photos database</span>
|
||||
|
||||
<span class="sd"> Filters out albums in the trash and any special album types</span>
|
||||
@@ -2866,20 +2788,21 @@
|
||||
<span class="sd"> Args:</span>
|
||||
<span class="sd"> shared: boolean; if True, returns shared albums, else normal albums</span>
|
||||
<span class="sd"> import_session: boolean, if True, returns import session albums, else normal or shared albums</span>
|
||||
<span class="sd"> project: boolean, if True, returns albums that are part of My Projects</span>
|
||||
<span class="sd"> Note: flags (shared, import_session) are mutually exclusive</span>
|
||||
|
||||
|
||||
<span class="sd"> Raises:</span>
|
||||
<span class="sd"> ValueError: raised if mutually exclusive flags passed</span>
|
||||
|
||||
<span class="sd"> Returns: list of album UUIDs</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">if</span> <span class="n">shared</span> <span class="ow">and</span> <span class="n">import_session</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="nb">sum</span><span class="p">(</span><span class="nb">bool</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="p">[</span><span class="n">shared</span><span class="p">,</span> <span class="n">import_session</span><span class="p">,</span> <span class="n">project</span><span class="p">])</span> <span class="o">></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">"flags are mutually exclusive: pass zero or one of shared, import_session"</span>
|
||||
<span class="s2">"flags are mutually exclusive: pass zero or one of shared, import_session, projects"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db_version</span> <span class="o"><=</span> <span class="n">_PHOTOS_4_VERSION</span><span class="p">:</span>
|
||||
<span class="n">version4</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="k">if</span> <span class="n">shared</span><span class="p">:</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span>
|
||||
<span class="sa">f</span><span class="s2">"Shared albums not implemented for Photos library version </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_db_version</span><span class="si">}</span><span class="s2">"</span>
|
||||
@@ -2890,16 +2813,44 @@
|
||||
<span class="sa">f</span><span class="s2">"Import sessions not implemented for Photos library version </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_db_version</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">return</span> <span class="p">[]</span> <span class="c1"># not implemented for _PHOTOS_4_VERSION</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">elif</span> <span class="n">project</span><span class="p">:</span>
|
||||
<span class="n">album_type</span> <span class="o">=</span> <span class="p">[</span>
|
||||
<span class="n">_PHOTOS_4_ALBUM_TYPE_PROJECT</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_4_ALBUM_TYPE_SLIDESHOW</span><span class="p">,</span>
|
||||
<span class="p">]</span>
|
||||
<span class="n">album_kind</span> <span class="o">=</span> <span class="n">_PHOTOS_4_ALBUM_KIND</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">version4</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
<span class="k">if</span> <span class="n">shared</span><span class="p">:</span>
|
||||
<span class="n">album_kind</span> <span class="o">=</span> <span class="n">_PHOTOS_5_SHARED_ALBUM_KIND</span>
|
||||
<span class="k">elif</span> <span class="n">import_session</span><span class="p">:</span>
|
||||
<span class="n">album_kind</span> <span class="o">=</span> <span class="n">_PHOTOS_5_IMPORT_SESSION_ALBUM_KIND</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">album_kind</span> <span class="o">=</span> <span class="n">_PHOTOS_5_ALBUM_KIND</span>
|
||||
<span class="n">album_type</span> <span class="o">=</span> <span class="p">[</span><span class="n">_PHOTOS_4_ALBUM_TYPE_ALBUM</span><span class="p">]</span>
|
||||
<span class="n">album_kind</span> <span class="o">=</span> <span class="n">_PHOTOS_4_ALBUM_KIND</span>
|
||||
|
||||
<span class="n">album_list</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="c1"># look through _dbalbum_details because _dbalbums_album won't have empty albums it</span>
|
||||
<span class="k">for</span> <span class="n">album</span><span class="p">,</span> <span class="n">detail</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_details</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="p">(</span>
|
||||
<span class="n">detail</span><span class="p">[</span><span class="s2">"kind"</span><span class="p">]</span> <span class="o">==</span> <span class="n">album_kind</span>
|
||||
<span class="ow">and</span> <span class="n">detail</span><span class="p">[</span><span class="s2">"albumType"</span><span class="p">]</span> <span class="ow">in</span> <span class="n">album_type</span>
|
||||
<span class="ow">and</span> <span class="ow">not</span> <span class="n">detail</span><span class="p">[</span><span class="s2">"intrash"</span><span class="p">]</span>
|
||||
<span class="ow">and</span> <span class="p">(</span>
|
||||
<span class="p">(</span><span class="n">shared</span> <span class="ow">and</span> <span class="n">detail</span><span class="p">[</span><span class="s2">"cloudownerhashedpersonid"</span><span class="p">]</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
|
||||
<span class="ow">or</span> <span class="p">(</span><span class="ow">not</span> <span class="n">shared</span> <span class="ow">and</span> <span class="n">detail</span><span class="p">[</span><span class="s2">"cloudownerhashedpersonid"</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
<span class="ow">and</span> <span class="n">detail</span><span class="p">[</span><span class="s2">"folderUuid"</span><span class="p">]</span> <span class="o">!=</span> <span class="n">_PHOTOS_4_ROOT_FOLDER</span>
|
||||
<span class="c1"># in Photos <= 4, special albums like "printAlbum" have kind _PHOTOS_4_ALBUM_KIND</span>
|
||||
<span class="c1"># but should not be listed here; they can be distinguished by looking</span>
|
||||
<span class="c1"># for folderUuid of _PHOTOS_4_ROOT_FOLDER as opposed to _PHOTOS_4_TOP_LEVEL_ALBUM</span>
|
||||
<span class="p">):</span>
|
||||
<span class="n">album_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">album</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">album_list</span>
|
||||
|
||||
<span class="c1"># Photos version 5+</span>
|
||||
<span class="k">if</span> <span class="n">shared</span><span class="p">:</span>
|
||||
<span class="n">album_kind</span> <span class="o">=</span> <span class="n">_PHOTOS_5_SHARED_ALBUM_KIND</span>
|
||||
<span class="k">elif</span> <span class="n">import_session</span><span class="p">:</span>
|
||||
<span class="n">album_kind</span> <span class="o">=</span> <span class="n">_PHOTOS_5_IMPORT_SESSION_ALBUM_KIND</span>
|
||||
<span class="k">elif</span> <span class="n">project</span><span class="p">:</span>
|
||||
<span class="n">album_kind</span> <span class="o">=</span> <span class="n">_PHOTOS_5_PROJECT_ALBUM_KIND</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">album_kind</span> <span class="o">=</span> <span class="n">_PHOTOS_5_ALBUM_KIND</span>
|
||||
|
||||
<span class="n">album_list</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="c1"># look through _dbalbum_details because _dbalbums_album won't have empty albums it</span>
|
||||
@@ -2911,13 +2862,6 @@
|
||||
<span class="p">(</span><span class="n">shared</span> <span class="ow">and</span> <span class="n">detail</span><span class="p">[</span><span class="s2">"cloudownerhashedpersonid"</span><span class="p">]</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
|
||||
<span class="ow">or</span> <span class="p">(</span><span class="ow">not</span> <span class="n">shared</span> <span class="ow">and</span> <span class="n">detail</span><span class="p">[</span><span class="s2">"cloudownerhashedpersonid"</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
<span class="ow">and</span> <span class="p">(</span>
|
||||
<span class="ow">not</span> <span class="n">version4</span>
|
||||
<span class="c1"># in Photos 4, special albums like "printAlbum" have kind _PHOTOS_4_ALBUM_KIND</span>
|
||||
<span class="c1"># but should not be listed here; they can be distinguished by looking</span>
|
||||
<span class="c1"># for folderUuid of _PHOTOS_4_ROOT_FOLDER as opposed to _PHOTOS_4_TOP_LEVEL_ALBUM</span>
|
||||
<span class="ow">or</span> <span class="p">(</span><span class="n">version4</span> <span class="ow">and</span> <span class="n">detail</span><span class="p">[</span><span class="s2">"folderUuid"</span><span class="p">]</span> <span class="o">!=</span> <span class="n">_PHOTOS_4_ROOT_FOLDER</span><span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
<span class="p">):</span>
|
||||
<span class="n">album_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">album</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">album_list</span>
|
||||
@@ -3020,6 +2964,7 @@
|
||||
<span class="k">if</span> <span class="n">keywords</span><span class="p">:</span>
|
||||
<span class="n">keyword_set</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
|
||||
<span class="k">for</span> <span class="n">keyword</span> <span class="ow">in</span> <span class="n">keywords</span><span class="p">:</span>
|
||||
<span class="n">keyword</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">keyword</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">keyword</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_keyword</span><span class="p">:</span>
|
||||
<span class="n">keyword_set</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbkeywords_keyword</span><span class="p">[</span><span class="n">keyword</span><span class="p">])</span>
|
||||
<span class="n">photos_sets</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">keyword_set</span><span class="p">)</span>
|
||||
@@ -3027,6 +2972,7 @@
|
||||
<span class="k">if</span> <span class="n">persons</span><span class="p">:</span>
|
||||
<span class="n">person_set</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
|
||||
<span class="k">for</span> <span class="n">person</span> <span class="ow">in</span> <span class="n">persons</span><span class="p">:</span>
|
||||
<span class="n">person</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">person</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">person</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbpersons_fullname</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">_dbpersons_fullname</span><span class="p">[</span><span class="n">person</span><span class="p">]:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
@@ -3058,6 +3004,7 @@
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">p</span><span class="p">][</span><span class="s2">"burst"</span><span class="p">]</span> <span class="ow">and</span> <span class="ow">not</span> <span class="p">(</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">p</span><span class="p">][</span><span class="s2">"burstPickType"</span><span class="p">]</span> <span class="o">&</span> <span class="n">BURST_SELECTED</span>
|
||||
<span class="ow">or</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">p</span><span class="p">][</span><span class="s2">"burstPickType"</span><span class="p">]</span> <span class="o">&</span> <span class="n">BURST_KEY</span>
|
||||
<span class="ow">or</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">p</span><span class="p">][</span><span class="s2">"burstPickType"</span><span class="p">]</span> <span class="o">==</span> <span class="n">BURST_PICK_TYPE_NONE</span>
|
||||
<span class="p">):</span>
|
||||
<span class="c1"># not a key/selected burst photo, don't include in returned results</span>
|
||||
<span class="k">continue</span>
|
||||
@@ -3068,8 +3015,6 @@
|
||||
<span class="p">):</span>
|
||||
<span class="n">info</span> <span class="o">=</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">p</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">p</span><span class="p">])</span>
|
||||
<span class="n">photoinfo</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">info</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">_debug</span><span class="p">:</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"photoinfo: </span><span class="si">{</span><span class="n">pformat</span><span class="p">(</span><span class="n">photoinfo</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">photoinfo</span></div>
|
||||
|
||||
@@ -3377,27 +3322,6 @@
|
||||
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">to_time</span><span class="p">:</span>
|
||||
<span class="n">photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">date</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o"><=</span> <span class="n">options</span><span class="o">.</span><span class="n">to_time</span><span class="p">]</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">burst_photos</span><span class="p">:</span>
|
||||
<span class="c1"># add the burst_photos to the export set</span>
|
||||
<span class="n">photos_burst</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">burst</span><span class="p">]</span>
|
||||
<span class="k">for</span> <span class="n">burst</span> <span class="ow">in</span> <span class="n">photos_burst</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">missing_bursts</span><span class="p">:</span>
|
||||
<span class="c1"># include burst photos that are missing</span>
|
||||
<span class="n">photos</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">burst</span><span class="o">.</span><span class="n">burst_photos</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="c1"># don't include missing burst images (these can't be downloaded with AppleScript)</span>
|
||||
<span class="n">photos</span><span class="o">.</span><span class="n">extend</span><span class="p">([</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">burst</span><span class="o">.</span><span class="n">burst_photos</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">p</span><span class="o">.</span><span class="n">ismissing</span><span class="p">])</span>
|
||||
|
||||
<span class="c1"># remove duplicates as each burst photo in the set that's selected would</span>
|
||||
<span class="c1"># result in the entire set being added above</span>
|
||||
<span class="c1"># can't use set() because PhotoInfo not hashable</span>
|
||||
<span class="n">seen_uuids</span> <span class="o">=</span> <span class="p">{}</span>
|
||||
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">uuid</span> <span class="ow">in</span> <span class="n">seen_uuids</span><span class="p">:</span>
|
||||
<span class="k">continue</span>
|
||||
<span class="n">seen_uuids</span><span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">uuid</span><span class="p">]</span> <span class="o">=</span> <span class="n">p</span>
|
||||
<span class="n">photos</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">seen_uuids</span><span class="o">.</span><span class="n">values</span><span class="p">())</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">name</span><span class="p">:</span>
|
||||
<span class="c1"># search filename fields for text</span>
|
||||
<span class="c1"># if more than one, find photos with all title values in filename</span>
|
||||
@@ -3406,23 +3330,35 @@
|
||||
<span class="c1"># case-insensitive</span>
|
||||
<span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">name</span><span class="p">:</span>
|
||||
<span class="n">n</span> <span class="o">=</span> <span class="n">n</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
|
||||
<span class="n">photo_list</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span>
|
||||
<span class="p">[</span>
|
||||
<span class="n">p</span>
|
||||
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span>
|
||||
<span class="k">if</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">p</span><span class="o">.</span><span class="n">filename</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
|
||||
<span class="ow">or</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">p</span><span class="o">.</span><span class="n">original_filename</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
|
||||
<span class="p">]</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db_version</span> <span class="o">>=</span> <span class="n">_PHOTOS_5_VERSION</span><span class="p">:</span>
|
||||
<span class="c1"># search only original_filename (#594)</span>
|
||||
<span class="n">photo_list</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span>
|
||||
<span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">p</span><span class="o">.</span><span class="n">original_filename</span><span class="o">.</span><span class="n">lower</span><span class="p">()]</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">photo_list</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span>
|
||||
<span class="p">[</span>
|
||||
<span class="n">p</span>
|
||||
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span>
|
||||
<span class="k">if</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">p</span><span class="o">.</span><span class="n">filename</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
|
||||
<span class="ow">or</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">p</span><span class="o">.</span><span class="n">original_filename</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
|
||||
<span class="p">]</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">name</span><span class="p">:</span>
|
||||
<span class="n">photo_list</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span>
|
||||
<span class="p">[</span>
|
||||
<span class="n">p</span>
|
||||
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span>
|
||||
<span class="k">if</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">p</span><span class="o">.</span><span class="n">filename</span> <span class="ow">or</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">p</span><span class="o">.</span><span class="n">original_filename</span>
|
||||
<span class="p">]</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db_version</span> <span class="o">>=</span> <span class="n">_PHOTOS_5_VERSION</span><span class="p">:</span>
|
||||
<span class="c1"># search only original_filename (#594)</span>
|
||||
<span class="n">photo_list</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span>
|
||||
<span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">p</span><span class="o">.</span><span class="n">original_filename</span><span class="p">]</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">photo_list</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span>
|
||||
<span class="p">[</span>
|
||||
<span class="n">p</span>
|
||||
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span>
|
||||
<span class="k">if</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">p</span><span class="o">.</span><span class="n">filename</span> <span class="ow">or</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">p</span><span class="o">.</span><span class="n">original_filename</span>
|
||||
<span class="p">]</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">photos</span> <span class="o">=</span> <span class="n">photo_list</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">min_size</span><span class="p">:</span>
|
||||
@@ -3536,6 +3472,28 @@
|
||||
<span class="k">for</span> <span class="n">function</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">function</span><span class="p">:</span>
|
||||
<span class="n">photos</span> <span class="o">=</span> <span class="n">function</span><span class="p">[</span><span class="mi">0</span><span class="p">](</span><span class="n">photos</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># burst should be checked last, ref #640</span>
|
||||
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">burst_photos</span><span class="p">:</span>
|
||||
<span class="c1"># add the burst_photos to the export set</span>
|
||||
<span class="n">photos_burst</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">burst</span><span class="p">]</span>
|
||||
<span class="k">for</span> <span class="n">burst</span> <span class="ow">in</span> <span class="n">photos_burst</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">missing_bursts</span><span class="p">:</span>
|
||||
<span class="c1"># include burst photos that are missing</span>
|
||||
<span class="n">photos</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">burst</span><span class="o">.</span><span class="n">burst_photos</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="c1"># don't include missing burst images (these can't be downloaded with AppleScript)</span>
|
||||
<span class="n">photos</span><span class="o">.</span><span class="n">extend</span><span class="p">([</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">burst</span><span class="o">.</span><span class="n">burst_photos</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">p</span><span class="o">.</span><span class="n">ismissing</span><span class="p">])</span>
|
||||
|
||||
<span class="c1"># remove duplicates as each burst photo in the set that's selected would</span>
|
||||
<span class="c1"># result in the entire set being added above</span>
|
||||
<span class="c1"># can't use set() because PhotoInfo not hashable</span>
|
||||
<span class="n">seen_uuids</span> <span class="o">=</span> <span class="p">{}</span>
|
||||
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">uuid</span> <span class="ow">in</span> <span class="n">seen_uuids</span><span class="p">:</span>
|
||||
<span class="k">continue</span>
|
||||
<span class="n">seen_uuids</span><span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">uuid</span><span class="p">]</span> <span class="o">=</span> <span class="n">p</span>
|
||||
<span class="n">photos</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">seen_uuids</span><span class="o">.</span><span class="n">values</span><span class="p">())</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">photos</span></div>
|
||||
|
||||
<div class="viewcode-block" id="PhotosDB.execute"><a class="viewcode-back" href="../../../reference.html#osxphotos.PhotosDB.execute">[docs]</a> <span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">sql</span><span class="p">):</span>
|
||||
@@ -3659,7 +3617,7 @@
|
||||
©2021, Rhet Turnbull.
|
||||
|
||||
|
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.3.2</a>
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.4.0</a>
|
||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -3,4 +3,6 @@ osxphotos command line interface (CLI)
|
||||
|
||||
.. click:: osxphotos.cli:cli
|
||||
:prog: osxphotos
|
||||
:nested: full
|
||||
:nested: full
|
||||
|
||||
.. program-output:: python3 -m osxphotos --help
|
||||
3
docs/_static/basic.css
vendored
3
docs/_static/basic.css
vendored
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* Sphinx stylesheet -- basic theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
|
||||
* :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
@@ -757,6 +757,7 @@ span.pre {
|
||||
-ms-hyphens: none;
|
||||
-webkit-hyphens: none;
|
||||
hyphens: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
div[class*="highlight-"] {
|
||||
|
||||
5
docs/_static/doctools.js
vendored
5
docs/_static/doctools.js
vendored
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* Sphinx JavaScript utilities for all documentation.
|
||||
*
|
||||
* :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
|
||||
* :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
@@ -264,6 +264,9 @@ var Documentation = {
|
||||
hideSearchWords : function() {
|
||||
$('#searchbox .highlight-link').fadeOut(300);
|
||||
$('span.highlighted').removeClass('highlighted');
|
||||
var url = new URL(window.location);
|
||||
url.searchParams.delete('highlight');
|
||||
window.history.replaceState({}, '', url);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
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.45.0',
|
||||
VERSION: '0.47.6',
|
||||
LANGUAGE: 'None',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
2
docs/_static/language_data.js
vendored
2
docs/_static/language_data.js
vendored
@@ -5,7 +5,7 @@
|
||||
* This script contains the language-specific data used by searchtools.js,
|
||||
* namely the list of stopwords, stemmer, scorer and splitter.
|
||||
*
|
||||
* :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
|
||||
* :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
2
docs/_static/searchtools.js
vendored
2
docs/_static/searchtools.js
vendored
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* Sphinx JavaScript utilities for the full-text search.
|
||||
*
|
||||
* :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
|
||||
* :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
|
||||
<title>osxphotos command line interface (CLI) — osxphotos 0.45.0 documentation</title>
|
||||
<title>osxphotos command line interface (CLI) — osxphotos 0.47.6 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
@@ -94,7 +94,7 @@
|
||||
©2021, Rhet Turnbull.
|
||||
|
||||
|
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.3.1</a>
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.4.0</a>
|
||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
||||
|
||||
|
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Index — osxphotos 0.45.0 documentation</title>
|
||||
<title>Index — osxphotos 0.47.6 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
@@ -34,8 +34,444 @@
|
||||
<h1 id="index">Index</h1>
|
||||
|
||||
<div class="genindex-jumpbox">
|
||||
<a href="#A"><strong>A</strong></a>
|
||||
| <a href="#B"><strong>B</strong></a>
|
||||
| <a href="#C"><strong>C</strong></a>
|
||||
| <a href="#D"><strong>D</strong></a>
|
||||
| <a href="#E"><strong>E</strong></a>
|
||||
| <a href="#F"><strong>F</strong></a>
|
||||
| <a href="#G"><strong>G</strong></a>
|
||||
| <a href="#H"><strong>H</strong></a>
|
||||
| <a href="#I"><strong>I</strong></a>
|
||||
| <a href="#J"><strong>J</strong></a>
|
||||
| <a href="#K"><strong>K</strong></a>
|
||||
| <a href="#L"><strong>L</strong></a>
|
||||
| <a href="#M"><strong>M</strong></a>
|
||||
| <a href="#O"><strong>O</strong></a>
|
||||
| <a href="#P"><strong>P</strong></a>
|
||||
| <a href="#Q"><strong>Q</strong></a>
|
||||
| <a href="#R"><strong>R</strong></a>
|
||||
| <a href="#S"><strong>S</strong></a>
|
||||
| <a href="#T"><strong>T</strong></a>
|
||||
| <a href="#U"><strong>U</strong></a>
|
||||
| <a href="#V"><strong>V</strong></a>
|
||||
| <a href="#W"><strong>W</strong></a>
|
||||
|
||||
</div>
|
||||
<h2 id="A">A</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.adjustments">adjustments (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.album_info">album_info (osxphotos.PhotoInfo property)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.album_info">(osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.album_info_shared">album_info_shared (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.albums">albums (osxphotos.PhotoInfo property)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.albums">(osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.albums_as_dict">albums_as_dict (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.albums_shared">albums_shared (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.albums_shared_as_dict">albums_shared_as_dict (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.asdict">asdict() (osxphotos.PhotoInfo method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="B">B</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.burst">burst (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.burst_album_info">burst_album_info (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.burst_albums">burst_albums (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.burst_default_pick">burst_default_pick (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.burst_key">burst_key (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.burst_photos">burst_photos (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.burst_selected">burst_selected (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="C">C</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.comments">comments (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="D">D</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.date">date (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.date_added">date_added (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.date_modified">date_modified (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.date_trashed">date_trashed (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.db_path">db_path (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.db_version">db_version (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.description">description (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.detected_text">detected_text() (osxphotos.PhotoInfo method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.duplicates">duplicates (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="E">E</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.execute">execute() (osxphotos.PhotosDB method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.exif_info">exif_info (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.exiftool">exiftool (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.export">export() (osxphotos.PhotoInfo method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.external_edit">external_edit (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="F">F</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.face_info">face_info (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.favorite">favorite (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.filename">filename (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.folder_info">folder_info (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.folders">folders (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="G">G</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.get_db_connection">get_db_connection() (osxphotos.PhotosDB method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.get_photo">get_photo() (osxphotos.PhotosDB method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="H">H</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.has_raw">has_raw (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.hasadjustments">hasadjustments (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.hdr">hdr (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.height">height (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.hidden">hidden (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="I">I</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.import_info">import_info (osxphotos.PhotoInfo property)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.import_info">(osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.incloud">incloud (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.intrash">intrash (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.iscloudasset">iscloudasset (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.ismissing">ismissing (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.ismovie">ismovie (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.isphoto">isphoto (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.israw">israw (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.isreference">isreference (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="J">J</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.json">json() (osxphotos.PhotoInfo method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="K">K</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.keywords">keywords (osxphotos.PhotoInfo property)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.keywords">(osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.keywords_as_dict">keywords_as_dict (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="L">L</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.labels">labels (osxphotos.PhotoInfo property)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.labels">(osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.labels_as_dict">labels_as_dict (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.labels_normalized">labels_normalized (osxphotos.PhotoInfo property)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.labels_normalized">(osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.labels_normalized_as_dict">labels_normalized_as_dict (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.library_path">library_path (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.likes">likes (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.live_photo">live_photo (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.location">location (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="M">M</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.moment">moment (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="O">O</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.orientation">orientation (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.original_filename">original_filename (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.original_filesize">original_filesize (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.original_height">original_height (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.original_orientation">original_orientation (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.original_width">original_width (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.owner">owner (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="P">P</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.panorama">panorama (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.path">path (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.path_derivatives">path_derivatives (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.path_edited">path_edited (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.path_edited_live_photo">path_edited_live_photo (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.path_live_photo">path_live_photo (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.path_raw">path_raw (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.person_info">person_info (osxphotos.PhotoInfo property)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.person_info">(osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.persons">persons (osxphotos.PhotoInfo property)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.persons">(osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.persons_as_dict">persons_as_dict (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo">PhotoInfo (class in osxphotos)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.photos">photos() (osxphotos.PhotosDB method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.photos_by_uuid">photos_by_uuid() (osxphotos.PhotosDB method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB">PhotosDB (class in osxphotos)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.place">place (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.portrait">portrait (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.project_info">project_info (osxphotos.PhotoInfo property)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.project_info">(osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="Q">Q</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.query">query() (osxphotos.PhotosDB method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="R">R</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.raw_original">raw_original (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.render_template">render_template() (osxphotos.PhotoInfo method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="S">S</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.score">score (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.screenshot">screenshot (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.search_info">search_info (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.search_info_normalized">search_info_normalized (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.selfie">selfie (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.shared">shared (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.slow_mo">slow_mo (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="T">T</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.time_lapse">time_lapse (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.title">title (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.tzoffset">tzoffset (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="U">U</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.uti">uti (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.uti_edited">uti_edited (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.uti_original">uti_original (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.uti_raw">uti_raw (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.uuid">uuid (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="V">V</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.visible">visible (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="W">W</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.width">width (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
@@ -92,7 +528,7 @@
|
||||
©2021, Rhet Turnbull.
|
||||
|
||||
|
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.3.1</a>
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.4.0</a>
|
||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
|
||||
<title>Welcome to osxphotos’s documentation! — osxphotos 0.45.0 documentation</title>
|
||||
<title>Welcome to osxphotos’s documentation! — osxphotos 0.47.6 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
@@ -50,7 +50,7 @@ You can also easily export both the original and edited photos.</p>
|
||||
<p>If you have access to macOS 12 / Monterey beta and would like to help ensure osxphotos is compatible, please contact me via GitHub.</p>
|
||||
<p>This package will read Photos databases for any supported version on any supported macOS version.
|
||||
E.g. you can read a database created with Photos 5.0 on MacOS 10.15 on a machine running macOS 10.12 and vice versa.</p>
|
||||
<p>Requires python >= <code class="docutils literal notranslate"><span class="pre">3.7</span></code>.</p>
|
||||
<p>Requires python >= <code class="docutils literal notranslate"><span class="pre">3.8</span></code>.</p>
|
||||
</section>
|
||||
<section id="installation">
|
||||
<h2>Installation<a class="headerlink" href="#installation" title="Permalink to this headline">¶</a></h2>
|
||||
@@ -355,7 +355,7 @@ Alternatively, you can also run the command line utility like this: <code class=
|
||||
©2021, Rhet Turnbull.
|
||||
|
||||
|
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.3.1</a>
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.4.0</a>
|
||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
||||
|
||||
|
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
|
||||
<title>osxphotos — osxphotos 0.45.0 documentation</title>
|
||||
<title>osxphotos — osxphotos 0.47.6 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
@@ -92,7 +92,7 @@
|
||||
©2021, Rhet Turnbull.
|
||||
|
||||
|
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.3.1</a>
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.4.0</a>
|
||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
||||
|
||||
|
|
||||
|
||||
BIN
docs/objects.inv
BIN
docs/objects.inv
Binary file not shown.
@@ -6,7 +6,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
|
||||
<title>osxphotos package — osxphotos 0.45.0 documentation</title>
|
||||
<title>osxphotos package — osxphotos 0.47.6 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
@@ -36,6 +36,883 @@
|
||||
<h1>osxphotos package<a class="headerlink" href="#osxphotos-package" title="Permalink to this headline">¶</a></h1>
|
||||
<section id="osxphotos-module">
|
||||
<h2>osxphotos module<a class="headerlink" href="#osxphotos-module" title="Permalink to this headline">¶</a></h2>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB">
|
||||
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">osxphotos.</span></span><span class="sig-name descname"><span class="pre">PhotosDB</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">dbfile</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">verbose</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">exiftool</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">rich</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photosdb/photosdb.html#PhotosDB"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotosDB" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Processes a Photos.app library database to extract information about photos</p>
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.album_info">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">album_info</span></span><a class="headerlink" href="#osxphotos.PhotosDB.album_info" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return list of AlbumInfo objects for each album in the photos database</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.album_info_shared">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">album_info_shared</span></span><a class="headerlink" href="#osxphotos.PhotosDB.album_info_shared" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>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</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.albums">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">albums</span></span><a class="headerlink" href="#osxphotos.PhotosDB.albums" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return list of albums found in photos database</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.albums_as_dict">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">albums_as_dict</span></span><a class="headerlink" href="#osxphotos.PhotosDB.albums_as_dict" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return albums as dict of albums, count in reverse sorted order (descending)</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.albums_shared">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">albums_shared</span></span><a class="headerlink" href="#osxphotos.PhotosDB.albums_shared" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return list of shared albums found in photos database
|
||||
only valid for Photos 5; on Photos <= 4, prints warning and returns empty list</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.albums_shared_as_dict">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">albums_shared_as_dict</span></span><a class="headerlink" href="#osxphotos.PhotosDB.albums_shared_as_dict" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns shared albums as dict of albums, count in reverse sorted order (descending)
|
||||
valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.db_path">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">db_path</span></span><a class="headerlink" href="#osxphotos.PhotosDB.db_path" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns path to the Photos library database PhotosDB was initialized with</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.db_version">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">db_version</span></span><a class="headerlink" href="#osxphotos.PhotosDB.db_version" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return the database version as stored in LiGlobals table</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.execute">
|
||||
<span class="sig-name descname"><span class="pre">execute</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">sql</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photosdb/photosdb.html#PhotosDB.execute"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotosDB.execute" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Execute sql statement and return cursor</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.folder_info">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">folder_info</span></span><a class="headerlink" href="#osxphotos.PhotosDB.folder_info" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return list FolderInfo objects representing top-level folders in the photos database</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.folders">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">folders</span></span><a class="headerlink" href="#osxphotos.PhotosDB.folders" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return list of top-level folder names in the photos database</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.get_db_connection">
|
||||
<span class="sig-name descname"><span class="pre">get_db_connection</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photosdb/photosdb.html#PhotosDB.get_db_connection"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotosDB.get_db_connection" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get connection to the working copy of the Photos database</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>tuple of (connection, cursor) to sqlite3 database</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.get_photo">
|
||||
<span class="sig-name descname"><span class="pre">get_photo</span></span><span class="sig-paren">(</span><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/photosdb/photosdb.html#PhotosDB.get_photo"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotosDB.get_photo" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns a single photo matching uuid</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>uuid</strong> – the UUID of photo to get</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>PhotoInfo instance for photo with UUID matching uuid or None if no match</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.import_info">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">import_info</span></span><a class="headerlink" href="#osxphotos.PhotosDB.import_info" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return list of ImportInfo objects for each import session in the database</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.keywords">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">keywords</span></span><a class="headerlink" href="#osxphotos.PhotosDB.keywords" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return list of keywords found in photos database</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.keywords_as_dict">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">keywords_as_dict</span></span><a class="headerlink" href="#osxphotos.PhotosDB.keywords_as_dict" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return keywords as dict of keyword, count in reverse sorted order (descending)</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.labels">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">labels</span></span><a class="headerlink" href="#osxphotos.PhotosDB.labels" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return list of all search info labels found in the library</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.labels_as_dict">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">labels_as_dict</span></span><a class="headerlink" href="#osxphotos.PhotosDB.labels_as_dict" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>count in reverse sorted order (descending)</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Type</dt>
|
||||
<dd class="field-odd"><p>return labels as dict of label</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.labels_normalized">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">labels_normalized</span></span><a class="headerlink" href="#osxphotos.PhotosDB.labels_normalized" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return list of all normalized search info labels found in the library</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.labels_normalized_as_dict">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">labels_normalized_as_dict</span></span><a class="headerlink" href="#osxphotos.PhotosDB.labels_normalized_as_dict" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>count in reverse sorted order (descending)</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Type</dt>
|
||||
<dd class="field-odd"><p>return normalized labels as dict of label</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.library_path">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">library_path</span></span><a class="headerlink" href="#osxphotos.PhotosDB.library_path" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns path to the Photos library PhotosDB was initialized with</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.person_info">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">person_info</span></span><a class="headerlink" href="#osxphotos.PhotosDB.person_info" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return list of PersonInfo objects for each person in the photos database</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.persons">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">persons</span></span><a class="headerlink" href="#osxphotos.PhotosDB.persons" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return list of persons found in photos database</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.persons_as_dict">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">persons_as_dict</span></span><a class="headerlink" href="#osxphotos.PhotosDB.persons_as_dict" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return persons as dict of person, count in reverse sorted order (descending)</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.photos">
|
||||
<span class="sig-name descname"><span class="pre">photos</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">keywords</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">uuid</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">persons</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">albums</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">images</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">movies</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">from_date</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">to_date</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">intrash</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photosdb/photosdb.html#PhotosDB.photos"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotosDB.photos" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Return a list of PhotoInfo objects
|
||||
If called with no args, returns the entire database of photos
|
||||
If called with args, returns photos matching the args (e.g. keywords, persons, etc.)
|
||||
If more than one arg, returns photos matching all the criteria (e.g. keywords AND persons)
|
||||
If more than one keyword, uuid, persons, albums is passed, they are treated as “OR” criteria
|
||||
e.g. keywords=[“wedding”,”vacation”] returns photos matching either keyword
|
||||
from_date and to_date may be either naive or timezone-aware datetime.datetime objects.
|
||||
If naive, timezone will be assumed to be local timezone.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>keywords</strong> – list of keywords to search for</p></li>
|
||||
<li><p><strong>uuid</strong> – list of UUIDs to search for</p></li>
|
||||
<li><p><strong>persons</strong> – list of persons to search for</p></li>
|
||||
<li><p><strong>albums</strong> – list of album names to search for</p></li>
|
||||
<li><p><strong>images</strong> – if True, returns image files, if False, does not return images; default is True</p></li>
|
||||
<li><p><strong>movies</strong> – if True, returns movie files, if False, does not return movies; default is True</p></li>
|
||||
<li><p><strong>from_date</strong> – return photos with creation date >= from_date (datetime.datetime object, default None)</p></li>
|
||||
<li><p><strong>to_date</strong> – return photos with creation date <= to_date (datetime.datetime object, default None)</p></li>
|
||||
<li><p><strong>intrash</strong> – if True, returns only images in “Recently deleted items” folder,
|
||||
if False returns only photos that aren’t deleted; default is False</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>list of PhotoInfo objects</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.photos_by_uuid">
|
||||
<span class="sig-name descname"><span class="pre">photos_by_uuid</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">uuids</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photosdb/photosdb.html#PhotosDB.photos_by_uuid"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotosDB.photos_by_uuid" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><dl class="simple">
|
||||
<dt>Returns a list of photos with UUID in uuids.</dt><dd><p>Does not generate error if invalid or missing UUID passed.
|
||||
This is faster than using PhotosDB.photos if you have list of UUIDs.
|
||||
Returns photos regardless of intrash state.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>uuid</strong> – list of UUIDs of photos to get</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>list of PhotoInfo instance for photo with UUID matching uuid or [] if no match</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.project_info">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">project_info</span></span><a class="headerlink" href="#osxphotos.PhotosDB.project_info" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return list of AlbumInfo projects for each project in the database</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.query">
|
||||
<span class="sig-name descname"><span class="pre">query</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">options</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">osxphotos.queryoptions.QueryOptions</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">List</span><span class="p"><span class="pre">[</span></span><a class="reference internal" href="#osxphotos.PhotoInfo" title="osxphotos.photoinfo.PhotoInfo"><span class="pre">osxphotos.photoinfo.PhotoInfo</span></a><span class="p"><span class="pre">]</span></span></span></span><a class="reference internal" href="_modules/osxphotos/photosdb/photosdb.html#PhotosDB.query"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotosDB.query" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Run a query against PhotosDB to extract the photos based on user supplied options</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>options</strong> – a QueryOptions instance</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo">
|
||||
<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">PhotoInfo</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">db</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">uuid</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">info</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photoinfo.html#PhotoInfo"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotoInfo" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Info about a specific photo, contains all the details about the photo
|
||||
including keywords, persons, albums, uuid, path, etc.</p>
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.adjustments">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">adjustments</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.adjustments" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns AdjustmentsInfo class for adjustment data or None if no adjustments; Photos 5+ only</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.album_info">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">album_info</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.album_info" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>list of AlbumInfo objects representing albums the photo is contained in</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.albums">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">albums</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.albums" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>list of albums picture is contained in</p>
|
||||
</dd></dl>
|
||||
|
||||
<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>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.burst">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">burst</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.burst" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is part of a Burst photo set, otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.burst_album_info">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">burst_album_info</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.burst_album_info" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>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.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.burst_albums">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">burst_albums</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.burst_albums" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>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</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.burst_default_pick">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">burst_default_pick</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.burst_default_pick" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is a burst image and is the photo that Photos selected as the default image for the burst set, otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.burst_key">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">burst_key</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.burst_key" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is a burst photo and is the key image for the burst set (the image that Photos shows on top of the burst stack), otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.burst_photos">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">burst_photos</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.burst_photos" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>If photo is a burst photo, returns list of PhotoInfo objects
|
||||
that are part of the same burst photo set; otherwise returns empty list.
|
||||
self is not included in the returned list</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.burst_selected">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">burst_selected</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.burst_selected" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is a burst photo and has been selected from the burst set by the user, otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.comments">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">comments</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.comments" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns list of Comment objects for any comments on the photo (sorted by date)</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.date">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">date</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.date" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>image creation date as timezone aware datetime object</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.date_added">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">date_added</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.date_added" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Date photo was added to the database</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.date_modified">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">date_modified</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.date_modified" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>image modification date as timezone aware datetime object
|
||||
or None if no modification date set</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.date_trashed">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">date_trashed</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.date_trashed" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Date asset was placed in the trash or None</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.description">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">description</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.description" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>long / extended description of picture</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.detected_text">
|
||||
<span class="sig-name descname"><span class="pre">detected_text</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">confidence_threshold</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0.75</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photoinfo.html#PhotoInfo.detected_text"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotoInfo.detected_text" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Detects text in photo and returns lists of results as (detected text, confidence)</p>
|
||||
<p>confidence_threshold: float between 0.0 and 1.0. If text detection confidence is below this threshold,
|
||||
text will not be returned. Default is TEXT_DETECTION_CONFIDENCE_THRESHOLD</p>
|
||||
<p>If photo is edited, uses the edited photo, otherwise the original; falls back to the preview image if neither edited or original is available</p>
|
||||
<p>Returns: list of (detected text, confidence) tuples</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.duplicates">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">duplicates</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.duplicates" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return list of PhotoInfo objects for possible duplicates (matching signature of original size, date, height, width) or empty list if no matching duplicates</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.exif_info">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">exif_info</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.exif_info" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns an ExifInfo object with the EXIF data for photo
|
||||
Note: the returned EXIF data is the data Photos stores in the database on import;
|
||||
ExifInfo does not provide access to the EXIF info in the actual image file
|
||||
Some or all of the fields may be None
|
||||
Only valid for Photos 5; on earlier database returns None</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.exiftool">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">exiftool</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.exiftool" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns a ExifToolCaching (read-only instance of ExifTool) object for the photo.
|
||||
Requires that exiftool (<a class="reference external" href="https://exiftool.org/">https://exiftool.org/</a>) be installed
|
||||
If exiftool not installed, logs warning and returns None
|
||||
If photo path is missing, returns None</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.export">
|
||||
<span class="sig-name descname"><span class="pre">export</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">dest</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">edited</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">live_photo</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">raw_photo</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">export_as_hardlink</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">overwrite</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">increment</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">sidecar_json</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">sidecar_exiftool</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">sidecar_xmp</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">use_photos_export</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">timeout</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">120</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">exiftool</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">use_albums_as_keywords</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">use_persons_as_keywords</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">keyword_template</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">description_template</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">render_options</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">osxphotos.phototemplate.RenderOptions</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photoinfo.html#PhotoInfo.export"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotoInfo.export" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>export photo
|
||||
dest: must be valid destination path (or exception raised)
|
||||
filename: (optional): name of exported picture; if not provided, will use current filename</p>
|
||||
<blockquote>
|
||||
<div><p><strong>NOTE</strong>: if provided, user must ensure file extension (suffix) is correct.
|
||||
For example, if photo is .CR2 file, edited image may be .jpeg.
|
||||
If you provide an extension different than what the actual file is,
|
||||
export will print a warning but will export the photo using the
|
||||
incorrect file extension (unless use_photos_export is true, in which case export will
|
||||
use the extension provided by Photos upon export; in this case, an incorrect extension is
|
||||
silently ignored).
|
||||
e.g. to get the extension of the edited photo,
|
||||
reference PhotoInfo.path_edited</p>
|
||||
</div></blockquote>
|
||||
<dl class="simple">
|
||||
<dt>edited: (boolean, default=False); if True will export the edited version of the photo, otherwise exports the original version</dt><dd><p>(or raise exception if no edited version)</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<p>live_photo: (boolean, default=False); if True, will also export the associated .mov for live photos
|
||||
raw_photo: (boolean, default=False); if True, will also export the associated RAW photo
|
||||
export_as_hardlink: (boolean, default=False); if True, will hardlink files instead of copying them
|
||||
overwrite: (boolean, default=False); if True will overwrite files if they already exist
|
||||
increment: (boolean, default=True); if True, will increment file name until a non-existant name is found</p>
|
||||
<blockquote>
|
||||
<div><p>if overwrite=False and increment=False, export will fail if destination file already exists</p>
|
||||
</div></blockquote>
|
||||
<dl class="simple">
|
||||
<dt>sidecar_json: if set will write a json sidecar with data in format readable by exiftool</dt><dd><p>sidecar filename will be dest/filename.json; includes exiftool tag group names (e.g. <cite>exiftool -G -j</cite>)</p>
|
||||
</dd>
|
||||
<dt>sidecar_exiftool: if set will write a json sidecar with data in format readable by exiftool</dt><dd><p>sidecar filename will be dest/filename.json; does not include exiftool tag group names (e.g. <cite>exiftool -j</cite>)</p>
|
||||
</dd>
|
||||
<dt>sidecar_xmp: if set will write an XMP sidecar with IPTC data</dt><dd><p>sidecar filename will be dest/filename.xmp</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<p>use_photos_export: (boolean, default=False); if True will attempt to export photo via applescript interaction with Photos
|
||||
timeout: (int, default=120) timeout in seconds used with use_photos_export
|
||||
exiftool: (boolean, default = False); if True, will use exiftool to write metadata to export file
|
||||
returns list of full paths to the exported files
|
||||
use_albums_as_keywords: (boolean, default = False); if True, will include album names in keywords
|
||||
when exporting metadata with exiftool or sidecar
|
||||
use_persons_as_keywords: (boolean, default = False); if True, will include person names in keywords
|
||||
when exporting metadata with exiftool or sidecar
|
||||
keyword_template: (list of strings); list of template strings that will be rendered as used as keywords
|
||||
description_template: string; optional template string that will be rendered for use as photo description
|
||||
render_options: an optional osxphotos.phototemplate.RenderOptions instance with options to pass to template renderer</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>list of photos exported</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.external_edit">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">external_edit</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.external_edit" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if picture was edited outside of Photos using external editor</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.face_info">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">face_info</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.face_info" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>list of FaceInfo objects for faces in picture</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.favorite">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">favorite</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.favorite" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>True if picture is marked as favorite</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.filename">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">filename</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.filename" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>filename of the picture</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.has_raw">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">has_raw</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.has_raw" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns True if photo has an associated raw image (that is, it’s a RAW+JPEG pair), otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.hasadjustments">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">hasadjustments</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.hasadjustments" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>True if picture has adjustments / edits</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.hdr">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">hdr</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.hdr" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is an HDR photo, otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.height">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">height</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.height" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns height of the current photo version in pixels</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.hidden">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">hidden</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.hidden" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>True if picture is hidden</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.import_info">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">import_info</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.import_info" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>ImportInfo object representing import session for the photo or None if no import session</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.incloud">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">incloud</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.incloud" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is cloud asset and is synched to cloud
|
||||
False if photo is cloud asset and not yet synched to cloud
|
||||
None if photo is not cloud asset</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.intrash">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">intrash</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.intrash" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>True if picture is in trash (‘Recently Deleted’ folder)</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.iscloudasset">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">iscloudasset</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.iscloudasset" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is a cloud asset (in an iCloud library),
|
||||
otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.ismissing">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">ismissing</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.ismissing" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns true if photo is missing from disk (which means it’s not been downloaded from iCloud)</p>
|
||||
<dl class="simple">
|
||||
<dt>NOTE: the photos.db database uses an asynchrounous write-ahead log so changes in Photos</dt><dd><p>do not immediately get written to disk. In particular, I’ve noticed that downloading
|
||||
an image from the cloud does not force the database to be updated until something else
|
||||
e.g. an edit, keyword, etc. occurs forcing a database synch
|
||||
The exact process / timing is a mystery to be but be aware that if some photos were recently
|
||||
downloaded from cloud to local storate their status in the database might still show
|
||||
isMissing = 1</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.ismovie">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">ismovie</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.ismovie" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if file is a movie, otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.isphoto">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">isphoto</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.isphoto" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if file is an image, otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.israw">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">israw</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.israw" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns True if photo is a raw image. For images with an associated RAW+JPEG pair, see has_raw</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.isreference">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">isreference</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.isreference" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is a reference (not copied to the Photos library), otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<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>
|
||||
<dd><p>Return JSON representation</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.keywords">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">keywords</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.keywords" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>list of keywords for picture</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.labels">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">labels</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.labels" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns list of labels applied to photo by Photos image categorization
|
||||
only valid on Photos 5, on older libraries returns empty list</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.labels_normalized">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">labels_normalized</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.labels_normalized" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns normalized list of labels applied to photo by Photos image categorization
|
||||
only valid on Photos 5, on older libraries returns empty list</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.likes">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">likes</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.likes" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns list of Like objects for any likes on the photo (sorted by date)</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.live_photo">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">live_photo</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.live_photo" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is a live photo, otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.location">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">location</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.location" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns (latitude, longitude) as float in degrees or None</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.moment">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">moment</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.moment" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Moment photo belongs to</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.orientation">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">orientation</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.orientation" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns EXIF orientation of the current photo version as int or 0 if current orientation cannot be determined</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.original_filename">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">original_filename</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.original_filename" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>original filename of the picture
|
||||
Photos 5 mangles filenames upon import</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.original_filesize">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">original_filesize</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.original_filesize" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns filesize of original photo in bytes as int</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.original_height">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">original_height</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.original_height" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns height of the original photo version in pixels</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.original_orientation">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">original_orientation</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.original_orientation" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns EXIF orientation of the original photo version as int</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.original_width">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">original_width</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.original_width" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns width of the original photo version in pixels</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.owner">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">owner</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.owner" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Return name of photo owner for shared photos (Photos 5+ only), or None if not shared</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.panorama">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">panorama</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.panorama" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is a panorama, otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.path">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">path</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.path" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>absolute path on disk of the original picture</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.path_derivatives">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">path_derivatives</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.path_derivatives" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first)</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.path_edited">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">path_edited</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.path_edited" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>absolute path on disk of the edited picture</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.path_edited_live_photo">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">path_edited_live_photo</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.path_edited_live_photo" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>return path to edited version of live photo movie; only valid for Photos 5+</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.path_live_photo">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">path_live_photo</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.path_live_photo" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns path to the associated video file for a live photo
|
||||
If photo is not a live photo, returns None
|
||||
If photo is missing, returns None</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.path_raw">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">path_raw</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.path_raw" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>absolute path of associated RAW image or None if there is not one</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.person_info">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">person_info</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.person_info" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>list of PersonInfo objects for person in picture</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.persons">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">persons</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.persons" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>list of persons in picture</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.place">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">place</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.place" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns PlaceInfo object containing reverse geolocation info</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.portrait">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">portrait</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.portrait" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is a portrait, otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.project_info">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">project_info</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.project_info" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>list of AlbumInfo objects representing projects for the photo or None if no projects</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.raw_original">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">raw_original</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.raw_original" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns True if associated raw image and the raw image is selected in Photos
|
||||
via “Use RAW as Original ”
|
||||
otherwise returns False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.render_template">
|
||||
<span class="sig-name descname"><span class="pre">render_template</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">template_str</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">options</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">osxphotos.phototemplate.RenderOptions</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photoinfo.html#PhotoInfo.render_template"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotoInfo.render_template" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Renders a template string for PhotoInfo instance using PhotoTemplate</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>template_str</strong> – a template string with fields to render</p></li>
|
||||
<li><p><strong>options</strong> – a RenderOptions instance</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>tuple of list of rendered strings and list of unmatched template values</p>
|
||||
</dd>
|
||||
<dt class="field-odd">Return type</dt>
|
||||
<dd class="field-odd"><p>([rendered_strings], [unmatched])</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.score">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">score</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.score" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Computed score information for a photo</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>ScoreInfo instance</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.screenshot">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">screenshot</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.screenshot" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is an HDR photo, otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.search_info">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">search_info</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.search_info" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns SearchInfo object for photo
|
||||
only valid on Photos 5, on older libraries, returns None</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.search_info_normalized">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">search_info_normalized</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.search_info_normalized" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns SearchInfo object for photo that produces normalized results
|
||||
only valid on Photos 5, on older libraries, returns None</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.selfie">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">selfie</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.selfie" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is a selfie (front facing camera), otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.shared">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">shared</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.shared" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns True if photos is in a shared iCloud album otherwise false
|
||||
Only valid on Photos 5; returns None on older versions</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.slow_mo">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">slow_mo</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.slow_mo" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is a slow motion video, otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.time_lapse">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">time_lapse</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.time_lapse" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns True if photo is a time lapse video, otherwise False</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.title">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">title</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.title" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>name / title of picture</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.tzoffset">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">tzoffset</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.tzoffset" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>timezone offset from UTC in seconds</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.uti">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">uti</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.uti" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns Uniform Type Identifier (UTI) for the image
|
||||
for example: public.jpeg or com.apple.quicktime-movie</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.uti_edited">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">uti_edited</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.uti_edited" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns Uniform Type Identifier (UTI) for the edited image
|
||||
if the photo has been edited, otherwise None;
|
||||
for example: public.jpeg</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.uti_original">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">uti_original</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.uti_original" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns Uniform Type Identifier (UTI) for the original image
|
||||
for example: public.jpeg or com.apple.quicktime-movie</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.uti_raw">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">uti_raw</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.uti_raw" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns Uniform Type Identifier (UTI) for the RAW image if there is one
|
||||
for example: com.canon.cr2-raw-image
|
||||
Returns None if no associated RAW image</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.uuid">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">uuid</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.uuid" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>UUID of picture</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.visible">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">visible</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.visible" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>True if picture is visble</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.width">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">width</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.width" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>returns width of the current photo version in pixels</p>
|
||||
</dd></dl>
|
||||
|
||||
</dd></dl>
|
||||
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -98,7 +975,7 @@
|
||||
©2021, Rhet Turnbull.
|
||||
|
||||
|
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.3.1</a>
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.4.0</a>
|
||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
||||
|
||||
|
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Search — osxphotos 0.45.0 documentation</title>
|
||||
<title>Search — osxphotos 0.47.6 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
©2021, Rhet Turnbull.
|
||||
|
||||
|
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.3.1</a>
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.4.0</a>
|
||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
||||
|
||||
</div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3,4 +3,6 @@ osxphotos command line interface (CLI)
|
||||
|
||||
.. click:: osxphotos.cli:cli
|
||||
:prog: osxphotos
|
||||
:nested: full
|
||||
:nested: full
|
||||
|
||||
.. program-output:: python3 -m osxphotos --help
|
||||
@@ -16,7 +16,7 @@ import sys
|
||||
|
||||
import sphinx_rtd_theme
|
||||
|
||||
sys.path.insert(0, os.path.abspath(".."))
|
||||
sys.path.insert(0, os.path.abspath("../.."))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
@@ -2,19 +2,27 @@
|
||||
# spec file for pyinstaller
|
||||
# run `pyinstaller osxphotos.spec`
|
||||
|
||||
|
||||
import os
|
||||
import importlib
|
||||
|
||||
pathex = os.getcwd()
|
||||
|
||||
from PyInstaller.utils.hooks import collect_data_files
|
||||
|
||||
# include necessary data files
|
||||
datas = [
|
||||
("osxphotos/templates/xmp_sidecar.mako", "osxphotos/templates"),
|
||||
("osxphotos/templates/xmp_sidecar_beta.mako", "osxphotos/templates"),
|
||||
("osxphotos/phototemplate.tx", "osxphotos"),
|
||||
("osxphotos/phototemplate.md", "osxphotos"),
|
||||
("osxphotos/tutorial.md", "osxphotos"),
|
||||
]
|
||||
datas = collect_data_files("osxphotos")
|
||||
datas.extend(
|
||||
[
|
||||
("osxphotos/templates/xmp_sidecar.mako", "osxphotos/templates"),
|
||||
("osxphotos/templates/xmp_sidecar_beta.mako", "osxphotos/templates"),
|
||||
("osxphotos/phototemplate.tx", "osxphotos"),
|
||||
("osxphotos/phototemplate.md", "osxphotos"),
|
||||
("osxphotos/tutorial.md", "osxphotos"),
|
||||
("osxphotos/exiftool_filetypes.json", "osxphotos"),
|
||||
]
|
||||
)
|
||||
|
||||
package_imports = [["photoscript", ["photoscript.applescript"]]]
|
||||
for package, files in package_imports:
|
||||
proot = os.path.dirname(importlib.import_module(package).__file__)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import logging
|
||||
|
||||
from ._constants import AlbumSortOrder
|
||||
from ._version import __version__
|
||||
from .debug import is_debug, set_debug
|
||||
from .exiftool import ExifTool
|
||||
from .export_db import ExportDB, ExportDBInMemory, ExportDBNoOp
|
||||
from .export_db import ExportDB
|
||||
from .fileutil import FileUtil, FileUtilNoOp
|
||||
from .momentinfo import MomentInfo
|
||||
from .personinfo import PersonInfo
|
||||
@@ -14,23 +17,24 @@ from .placeinfo import PlaceInfo
|
||||
from .queryoptions import QueryOptions
|
||||
from .scoreinfo import ScoreInfo
|
||||
from .searchinfo import SearchInfo
|
||||
from .utils import _debug, _get_logger, _set_debug
|
||||
from .utils import _get_logger
|
||||
|
||||
if not is_debug():
|
||||
logging.disable(logging.DEBUG)
|
||||
|
||||
__all__ = [
|
||||
"__version__",
|
||||
"_debug",
|
||||
"_get_logger",
|
||||
"_set_debug",
|
||||
"AlbumSortOrder",
|
||||
"CommentInfo",
|
||||
"ExifTool",
|
||||
"ExportDB",
|
||||
"ExportDBInMemory",
|
||||
"ExportDBNoOp",
|
||||
"ExportDBTemp",
|
||||
"ExportOptions",
|
||||
"ExportResults",
|
||||
"FileUtil",
|
||||
"FileUtilNoOp",
|
||||
"is_debug",
|
||||
"LikeInfo",
|
||||
"MomentInfo",
|
||||
"PersonInfo",
|
||||
@@ -42,4 +46,5 @@ __all__ = [
|
||||
"QueryOptions",
|
||||
"ScoreInfo",
|
||||
"SearchInfo",
|
||||
"set_debug",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Command line interface for osxphotos """
|
||||
|
||||
from .cli import cli
|
||||
from .cli.cli import cli_main
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli() # pylint: disable=no-value-for-parameter
|
||||
cli_main()
|
||||
|
||||
@@ -103,6 +103,8 @@ _TESTED_OS_VERSIONS = [
|
||||
("11", "6"),
|
||||
("12", "0"),
|
||||
("12", "1"),
|
||||
("12", "2"),
|
||||
("12", "3"),
|
||||
]
|
||||
|
||||
# Photos 5 has persons who are empty string if unidentified face
|
||||
@@ -231,10 +233,6 @@ DEFAULT_ORIGINAL_SUFFIX = ""
|
||||
# Default suffix to add to preview images
|
||||
DEFAULT_PREVIEW_SUFFIX = "_preview"
|
||||
|
||||
# Colors for print CLI messages
|
||||
CLI_COLOR_ERROR = "red"
|
||||
CLI_COLOR_WARNING = "yellow"
|
||||
|
||||
# Bit masks for --sidecar
|
||||
SIDECAR_JSON = 0x1
|
||||
SIDECAR_EXIFTOOL = 0x2
|
||||
@@ -259,6 +257,7 @@ EXTENDED_ATTRIBUTE_NAMES = [
|
||||
]
|
||||
EXTENDED_ATTRIBUTE_NAMES_QUOTED = [f"'{x}'" for x in EXTENDED_ATTRIBUTE_NAMES]
|
||||
|
||||
|
||||
# name of export DB
|
||||
OSXPHOTOS_EXPORT_DB = ".osxphotos_export.db"
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.45.2"
|
||||
__version__ = "0.47.6"
|
||||
|
||||
97
osxphotos/cli/__init__.py
Normal file
97
osxphotos/cli/__init__.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""cli package for osxphotos"""
|
||||
import sys
|
||||
|
||||
from rich import print
|
||||
from rich.traceback import install as install_traceback
|
||||
|
||||
from osxphotos.debug import (
|
||||
debug_breakpoint,
|
||||
debug_watch,
|
||||
get_debug_flags,
|
||||
get_debug_options,
|
||||
set_debug,
|
||||
wrap_function,
|
||||
)
|
||||
|
||||
# apply any debug functions
|
||||
# need to do this before importing anything else so that the debug functions
|
||||
# wrap the right function references
|
||||
# if a module does something like "from exiftool import ExifTool" and the user tries
|
||||
# to wrap 'osxphotos.exiftool.ExifTool.asdict', the original ExifTool.asdict will be
|
||||
# wrapped but the caller will have a reference to the function before it was wrapped
|
||||
# reference: https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/13-ordering-issues-when-monkey-patching-in-python.md
|
||||
args = get_debug_options(["--watch", "--breakpoint"], sys.argv)
|
||||
for func_name in args.get("--watch", []):
|
||||
try:
|
||||
wrap_function(func_name, debug_watch)
|
||||
print(f"Watching {func_name}")
|
||||
except AttributeError:
|
||||
print(f"{func_name} does not exist")
|
||||
sys.exit(1)
|
||||
|
||||
for func_name in args.get("--breakpoint", []):
|
||||
try:
|
||||
wrap_function(func_name, debug_breakpoint)
|
||||
print(f"Breakpoint added for {func_name}")
|
||||
except AttributeError:
|
||||
print(f"{func_name} does not exist")
|
||||
sys.exit(1)
|
||||
|
||||
args = get_debug_flags(["--debug"], sys.argv)
|
||||
if args.get("--debug", False):
|
||||
set_debug(True)
|
||||
print("Debugging enabled")
|
||||
|
||||
from .about import about
|
||||
from .albums import albums
|
||||
from .cli import cli_main
|
||||
from .common import get_photos_db, load_uuid_from_file
|
||||
from .debug_dump import debug_dump
|
||||
from .dump import dump
|
||||
from .export import export
|
||||
from .exportdb import exportdb
|
||||
from .grep import grep
|
||||
from .help import help
|
||||
from .info import info
|
||||
from .install_uninstall_run import install, run, uninstall
|
||||
from .keywords import keywords
|
||||
from .labels import labels
|
||||
from .list import _list_libraries, list_libraries
|
||||
from .persons import persons
|
||||
from .places import places
|
||||
from .query import query
|
||||
from .repl import repl
|
||||
from .snap_diff import diff, snap
|
||||
from .tutorial import tutorial
|
||||
from .uuid import uuid
|
||||
|
||||
install_traceback()
|
||||
|
||||
__all__ = [
|
||||
"about",
|
||||
"albums",
|
||||
"cli_main",
|
||||
"debug_dump",
|
||||
"diff",
|
||||
"dump",
|
||||
"export",
|
||||
"exportdb",
|
||||
"grep",
|
||||
"help",
|
||||
"info",
|
||||
"install",
|
||||
"keywords",
|
||||
"labels",
|
||||
"list_libraries",
|
||||
"list_libraries",
|
||||
"load_uuid_from_file",
|
||||
"persons",
|
||||
"places",
|
||||
"query",
|
||||
"repl",
|
||||
"run",
|
||||
"set_debug",
|
||||
"snap",
|
||||
"tutorial",
|
||||
"uuid",
|
||||
]
|
||||
66
osxphotos/cli/about.py
Normal file
66
osxphotos/cli/about.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""about command for osxphotos CLI"""
|
||||
|
||||
import click
|
||||
|
||||
from osxphotos._constants import OSXPHOTOS_URL
|
||||
from osxphotos._version import __version__
|
||||
|
||||
|
||||
@click.command(name="about")
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
def about(ctx, cli_obj):
|
||||
"""Print information about osxphotos including license."""
|
||||
license = """
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019-2021 Rhet Turnbull
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
osxphotos uses the following 3rd party software licensed under the BSD-3-Clause License:
|
||||
Click (Copyright 2014 Pallets), ptpython (Copyright (c) 2015, Jonathan Slenders)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are
|
||||
permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list
|
||||
of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
of conditions and the following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
used to endorse or promote products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
click.echo(f"osxphotos, version {__version__}")
|
||||
click.echo("")
|
||||
click.echo(f"Source code available at: {OSXPHOTOS_URL}")
|
||||
click.echo(license)
|
||||
42
osxphotos/cli/albums.py
Normal file
42
osxphotos/cli/albums.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""albums command for osxphotos CLI"""
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
import yaml
|
||||
|
||||
import osxphotos
|
||||
|
||||
from .common import DB_ARGUMENT, DB_OPTION, JSON_OPTION, get_photos_db
|
||||
from .list import _list_libraries
|
||||
|
||||
from osxphotos._constants import _PHOTOS_4_VERSION
|
||||
|
||||
|
||||
@click.command()
|
||||
@DB_OPTION
|
||||
@JSON_OPTION
|
||||
@DB_ARGUMENT
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
def albums(ctx, cli_obj, db, json_, photos_library):
|
||||
"""Print out albums found in the Photos library."""
|
||||
|
||||
# below needed for to make CliRunner work for testing
|
||||
cli_db = cli_obj.db if cli_obj is not None else None
|
||||
db = get_photos_db(*photos_library, db, cli_db)
|
||||
if db is None:
|
||||
click.echo(ctx.obj.group.commands["albums"].get_help(ctx), err=True)
|
||||
click.echo("\n\nLocated the following Photos library databases: ", err=True)
|
||||
_list_libraries()
|
||||
return
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=db)
|
||||
albums = {"albums": photosdb.albums_as_dict}
|
||||
if photosdb.db_version > _PHOTOS_4_VERSION:
|
||||
albums["shared albums"] = photosdb.albums_shared_as_dict
|
||||
|
||||
if json_ or cli_obj.json:
|
||||
click.echo(json.dumps(albums, ensure_ascii=False))
|
||||
else:
|
||||
click.echo(yaml.dump(albums, sort_keys=False, allow_unicode=True))
|
||||
84
osxphotos/cli/cli.py
Normal file
84
osxphotos/cli/cli.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""Command line interface for osxphotos """
|
||||
|
||||
import click
|
||||
|
||||
import osxphotos
|
||||
from osxphotos._version import __version__
|
||||
|
||||
from .about import about
|
||||
from .albums import albums
|
||||
from .common import DB_OPTION, JSON_OPTION, OSXPHOTOS_HIDDEN
|
||||
from .debug_dump import debug_dump
|
||||
from .dump import dump
|
||||
from .export import export
|
||||
from .exportdb import exportdb
|
||||
from .grep import grep
|
||||
from .help import help
|
||||
from .info import info
|
||||
from .install_uninstall_run import install, run, uninstall
|
||||
from .keywords import keywords
|
||||
from .labels import labels
|
||||
from .list import _list_libraries, list_libraries
|
||||
from .persons import persons
|
||||
from .places import places
|
||||
from .query import query
|
||||
from .repl import repl
|
||||
from .snap_diff import diff, snap
|
||||
from .tutorial import tutorial
|
||||
from .uuid import uuid
|
||||
|
||||
|
||||
# Click CLI object & context settings
|
||||
class CLI_Obj:
|
||||
def __init__(self, db=None, json=False, debug=False, group=None):
|
||||
self.db = db
|
||||
self.json = json
|
||||
self.group = group
|
||||
|
||||
|
||||
CTX_SETTINGS = dict(help_option_names=["-h", "--help"])
|
||||
|
||||
|
||||
@click.group(context_settings=CTX_SETTINGS)
|
||||
@DB_OPTION
|
||||
@JSON_OPTION
|
||||
@click.option(
|
||||
"--debug",
|
||||
required=False,
|
||||
is_flag=True,
|
||||
help="Enable debug output",
|
||||
hidden=OSXPHOTOS_HIDDEN,
|
||||
)
|
||||
@click.version_option(__version__, "--version", "-v")
|
||||
@click.pass_context
|
||||
def cli_main(ctx, db, json_, debug):
|
||||
ctx.obj = CLI_Obj(db=db, json=json_, group=cli_main)
|
||||
|
||||
|
||||
# install CLI commands
|
||||
for command in [
|
||||
about,
|
||||
albums,
|
||||
debug_dump,
|
||||
diff,
|
||||
dump,
|
||||
export,
|
||||
exportdb,
|
||||
grep,
|
||||
help,
|
||||
info,
|
||||
install,
|
||||
keywords,
|
||||
labels,
|
||||
list_libraries,
|
||||
persons,
|
||||
places,
|
||||
query,
|
||||
repl,
|
||||
run,
|
||||
snap,
|
||||
tutorial,
|
||||
uninstall,
|
||||
uuid,
|
||||
]:
|
||||
cli_main.add_command(command)
|
||||
272
osxphotos/cli/click_rich_echo.py
Normal file
272
osxphotos/cli/click_rich_echo.py
Normal file
@@ -0,0 +1,272 @@
|
||||
"""click.echo replacement that supports rich text formatting"""
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import typing as t
|
||||
from io import StringIO
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
from rich.markdown import Markdown
|
||||
from rich.theme import Theme
|
||||
|
||||
from .common import time_stamp
|
||||
|
||||
__all__ = [
|
||||
"get_rich_console",
|
||||
"get_rich_theme",
|
||||
"rich_click_echo",
|
||||
"rich_echo",
|
||||
"rich_echo_error",
|
||||
"rich_echo_via_pager",
|
||||
"set_rich_console",
|
||||
"set_rich_theme",
|
||||
"set_rich_timestamp",
|
||||
]
|
||||
|
||||
# TODO: this should really be a class instead of a module with a bunch of globals
|
||||
|
||||
# include emoji's in rich_echo_error output
|
||||
ERROR_EMOJI = True
|
||||
|
||||
|
||||
class _Console:
|
||||
"""Store console object for rich output"""
|
||||
|
||||
def __init__(self):
|
||||
self._console: t.Optional[Console] = None
|
||||
|
||||
@property
|
||||
def console(self):
|
||||
return self._console
|
||||
|
||||
@console.setter
|
||||
def console(self, console: Console):
|
||||
self._console = console
|
||||
|
||||
|
||||
_console = _Console()
|
||||
|
||||
_theme = None
|
||||
|
||||
_timestamp = False
|
||||
|
||||
# set to 1 if running tests
|
||||
OSXPHOTOS_IS_TESTING = bool(os.getenv("OSXPHOTOS_IS_TESTING", default=False))
|
||||
|
||||
|
||||
def set_rich_console(console: Console) -> None:
|
||||
"""Set the console object to use for rich_echo and rich_echo_via_pager"""
|
||||
global _console
|
||||
_console.console = console
|
||||
|
||||
|
||||
def get_rich_console() -> Console:
|
||||
"""Get console object
|
||||
|
||||
Returns:
|
||||
Console object
|
||||
"""
|
||||
global _console
|
||||
return _console.console
|
||||
|
||||
|
||||
def set_rich_theme(theme: Theme) -> None:
|
||||
"""Set the theme to use for rich_click_echo"""
|
||||
global _theme
|
||||
_theme = theme
|
||||
|
||||
|
||||
def get_rich_theme() -> t.Optional[Theme]:
|
||||
"""Get the theme to use for rich_click_echo"""
|
||||
global _theme
|
||||
return _theme
|
||||
|
||||
|
||||
def set_rich_timestamp(timestamp: bool) -> None:
|
||||
"""Set whether to print timestamp with rich_echo, rich_echo_error, and rich_click_error"""
|
||||
global _timestamp
|
||||
_timestamp = timestamp
|
||||
|
||||
|
||||
def rich_echo(
|
||||
message: t.Optional[t.Any] = None,
|
||||
theme=None,
|
||||
markdown=False,
|
||||
highlight=False,
|
||||
**kwargs: t.Any,
|
||||
) -> None:
|
||||
"""Echo text to the console with rich formatting.
|
||||
|
||||
Args:
|
||||
message: The string or bytes to output. Other objects are converted to strings.
|
||||
theme: optional rich.theme.Theme object to use for formatting
|
||||
markdown: if True, interpret message as Markdown
|
||||
highlight: if True, use automatic rich.print highlighting
|
||||
kwargs: any extra arguments are passed to rich.console.Console.print() and click.echo
|
||||
if kwargs contains 'file', 'nl', 'err', 'color', these are passed to click.echo,
|
||||
all other values passed to rich.console.Console.print()
|
||||
"""
|
||||
|
||||
# args for click.echo that may have been passed in kwargs
|
||||
echo_args = {}
|
||||
for arg in ("file", "nl", "err", "color"):
|
||||
val = kwargs.pop(arg, None)
|
||||
if val is not None:
|
||||
echo_args[arg] = val
|
||||
|
||||
width = kwargs.pop("width", None)
|
||||
if width is None and OSXPHOTOS_IS_TESTING:
|
||||
# if not outputting to terminal, use a huge width to avoid wrapping
|
||||
# otherwise tests fail
|
||||
width = 10_000
|
||||
console = get_rich_console() or Console(theme=theme, width=width)
|
||||
if markdown:
|
||||
message = Markdown(message)
|
||||
# Markdown always adds a new line so disable unless explicitly specified
|
||||
global _timestamp
|
||||
if _timestamp:
|
||||
message = time_stamp() + message
|
||||
console.print(message, highlight=highlight, **kwargs)
|
||||
|
||||
|
||||
def rich_echo_error(
|
||||
message: t.Optional[t.Any] = None,
|
||||
theme=None,
|
||||
markdown=False,
|
||||
highlight=False,
|
||||
**kwargs: t.Any,
|
||||
) -> None:
|
||||
"""Echo text to the console with rich formatting and if stdout is redirected, echo to stderr
|
||||
|
||||
Args:
|
||||
message: The string or bytes to output. Other objects are converted to strings.
|
||||
theme: optional rich.theme.Theme object to use for formatting
|
||||
markdown: if True, interpret message as Markdown
|
||||
highlight: if True, use automatic rich.print highlighting
|
||||
kwargs: any extra arguments are passed to rich.console.Console.print() and click.echo
|
||||
if kwargs contains 'file', 'nl', 'err', 'color', these are passed to click.echo,
|
||||
all other values passed to rich.console.Console.print()
|
||||
"""
|
||||
|
||||
global ERROR_EMOJI
|
||||
if ERROR_EMOJI:
|
||||
if "[error]" in message:
|
||||
message = f":cross_mark-emoji: {message}"
|
||||
elif "[warning]" in message:
|
||||
message = f":warning-emoji: {message}"
|
||||
|
||||
console = get_rich_console() or Console(theme=theme or get_rich_theme())
|
||||
if not console.is_terminal:
|
||||
# if stdout is redirected, echo to stderr
|
||||
rich_click_echo(
|
||||
message,
|
||||
theme=theme or get_rich_theme(),
|
||||
markdown=markdown,
|
||||
highlight=highlight,
|
||||
**kwargs,
|
||||
err=True,
|
||||
)
|
||||
else:
|
||||
rich_echo(
|
||||
message,
|
||||
theme=theme or get_rich_theme(),
|
||||
markdown=markdown,
|
||||
highlight=highlight,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def rich_click_echo(
|
||||
message: t.Optional[t.Any] = None,
|
||||
theme=None,
|
||||
markdown=False,
|
||||
highlight=False,
|
||||
**kwargs: t.Any,
|
||||
) -> None:
|
||||
"""Echo text to the console with rich formatting using click.echo
|
||||
|
||||
This is a wrapper around click.echo that supports rich text formatting.
|
||||
|
||||
Args:
|
||||
message: The string or bytes to output. Other objects are converted to strings.
|
||||
theme: optional rich.theme.Theme object to use for formatting
|
||||
markdown: if True, interpret message as Markdown
|
||||
highlight: if True, use automatic rich.print highlighting
|
||||
kwargs: any extra arguments are passed to rich.console.Console.print() and click.echo
|
||||
if kwargs contains 'file', 'nl', 'err', 'color', these are passed to click.echo,
|
||||
all other values passed to rich.console.Console.print()
|
||||
"""
|
||||
|
||||
# args for click.echo that may have been passed in kwargs
|
||||
echo_args = {}
|
||||
for arg in ("file", "nl", "err", "color"):
|
||||
val = kwargs.pop(arg, None)
|
||||
if val is not None:
|
||||
echo_args[arg] = val
|
||||
|
||||
# click.echo will include "\n" so don't add it here unless specified
|
||||
end = kwargs.pop("end", "")
|
||||
|
||||
if width := kwargs.pop("width", None) is None:
|
||||
# if not outputting to terminal, use a huge width to avoid wrapping
|
||||
# otherwise tests fail
|
||||
temp_console = Console()
|
||||
width = temp_console.width if temp_console.is_terminal else 10_000
|
||||
output = StringIO()
|
||||
console = Console(
|
||||
force_terminal=True,
|
||||
theme=theme or get_rich_theme(),
|
||||
file=output,
|
||||
width=width,
|
||||
)
|
||||
if markdown:
|
||||
message = Markdown(message)
|
||||
# Markdown always adds a new line so disable unless explicitly specified
|
||||
echo_args["nl"] = echo_args.get("nl") is True
|
||||
global _timestamp
|
||||
if _timestamp:
|
||||
message = time_stamp() + message
|
||||
console.print(message, end=end, highlight=highlight, **kwargs)
|
||||
click.echo(output.getvalue(), **echo_args)
|
||||
|
||||
|
||||
def rich_echo_via_pager(
|
||||
text_or_generator: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str],
|
||||
theme: t.Optional[Theme] = None,
|
||||
highlight=False,
|
||||
markdown: bool = False,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""This function takes a text and shows it via an environment specific
|
||||
pager on stdout.
|
||||
|
||||
Args:
|
||||
text_or_generator: the text to page, or alternatively, a generator emitting the text to page.
|
||||
theme: optional rich.theme.Theme object to use for formatting
|
||||
markdown: if True, interpret message as Markdown
|
||||
highlight: if True, use automatic rich.print highlighting
|
||||
**kwargs: if "color" in kwargs, works the same as click.echo_via_pager(color=color)
|
||||
otherwise any kwargs are passed to rich.Console.print()
|
||||
"""
|
||||
if inspect.isgeneratorfunction(text_or_generator):
|
||||
text_or_generator = t.cast(t.Callable[[], t.Iterable[str]], text_or_generator)()
|
||||
elif isinstance(text_or_generator, str):
|
||||
text_or_generator = [text_or_generator]
|
||||
else:
|
||||
try:
|
||||
text_or_generator = iter(text_or_generator)
|
||||
except TypeError:
|
||||
text_or_generator = [text_or_generator]
|
||||
|
||||
console = _console or Console(theme=theme)
|
||||
|
||||
color = kwargs.pop("color", None)
|
||||
if color is None:
|
||||
color = bool(console.color_system)
|
||||
|
||||
with console.pager(styles=color):
|
||||
for x in text_or_generator:
|
||||
if isinstance(x, str) and markdown:
|
||||
x = Markdown(x)
|
||||
console.print(x, highlight=highlight, **kwargs)
|
||||
124
osxphotos/cli/color_themes.py
Normal file
124
osxphotos/cli/color_themes.py
Normal file
@@ -0,0 +1,124 @@
|
||||
"""Support for colorized output for photos_time_warp"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from rich.style import Style
|
||||
from rich.themes import Theme
|
||||
|
||||
from .common import noop
|
||||
from .darkmode import is_dark_mode
|
||||
|
||||
__all__ = ["get_theme"]
|
||||
|
||||
|
||||
COLOR_THEMES = {
|
||||
"dark": Theme(
|
||||
{
|
||||
# color pallette from https://github.com/dracula/dracula-theme
|
||||
"color": Style(color="rgb(248,248,242)"),
|
||||
"count": Style(color="rgb(139,233,253)"),
|
||||
"error": Style(color="rgb(255,85,85)", bold=True),
|
||||
"filename": Style(color="rgb(189,147,249)", bold=True),
|
||||
"filepath": Style(color="rgb(80,250,123)", bold=True),
|
||||
"highlight": Style(color="#000000", bgcolor="#d73a49", bold=True),
|
||||
"num": Style(color="rgb(139,233,253)", bold=True),
|
||||
"time": Style(color="rgb(139,233,253)", bold=True),
|
||||
"uuid": Style(color="rgb(255,184,108)"),
|
||||
"warning": Style(color="rgb(241,250,140)", bold=True),
|
||||
"bar.back": Style(color="rgb(68,71,90)"),
|
||||
"bar.complete": Style(color="rgb(249,38,114)"),
|
||||
"bar.finished": Style(color="rgb(80,250,123)"),
|
||||
"bar.pulse": Style(color="rgb(98,114,164)"),
|
||||
"progress.elapsed": Style(color="rgb(139,233,253)"),
|
||||
"progress.percentage": Style(color="rgb(255,121,198)"),
|
||||
"progress.remaining": Style(color="rgb(139,233,253)"),
|
||||
}
|
||||
),
|
||||
"light": Theme(
|
||||
{
|
||||
"color": Style(color="#000000"),
|
||||
"count": Style(color="#005cc5", bold=True),
|
||||
"error": Style(color="#b31d28", bold=True, underline=True, italic=True),
|
||||
"filename": Style(color="#6f42c1", bold=True),
|
||||
"filepath": Style(color="#22863a", bold=True),
|
||||
"highlight": Style(color="#ffffff", bgcolor="#d73a49", bold=True),
|
||||
"num": Style(color="#005cc5", bold=True),
|
||||
"time": Style(color="#032f62", bold=True),
|
||||
"uuid": Style(color="#d73a49", bold=True),
|
||||
"warning": Style(color="#e36209", bold=True, underline=True, italic=True),
|
||||
"bar.back": Style(color="grey23"),
|
||||
"bar.complete": Style(color="rgb(249,38,114)"),
|
||||
"bar.finished": Style(color="rgb(114,156,31)"),
|
||||
"bar.pulse": Style(color="rgb(249,38,114)"),
|
||||
"progress.elapsed": Style(color="#032f62", bold=True),
|
||||
"progress.percentage": Style(color="#6f42c1", bold=True),
|
||||
"progress.remaining": Style(color="#032f62", bold=True),
|
||||
}
|
||||
),
|
||||
"mono": Theme(
|
||||
{
|
||||
"count": "bold",
|
||||
"error": "reverse italic",
|
||||
"filename": "bold",
|
||||
"filepath": "bold underline",
|
||||
"highlight": "reverse italic",
|
||||
"num": "bold",
|
||||
"time": "bold",
|
||||
"uuid": "bold",
|
||||
"warning": "bold italic",
|
||||
"bar.back": "",
|
||||
"bar.complete": "reverse",
|
||||
"bar.finished": "bold",
|
||||
"bar.pulse": "bold",
|
||||
"progress.elapsed": "",
|
||||
"progress.percentage": "bold",
|
||||
"progress.remaining": "bold",
|
||||
}
|
||||
),
|
||||
"plain": Theme(
|
||||
{
|
||||
"color": "",
|
||||
"count": "",
|
||||
"error": "",
|
||||
"filename": "",
|
||||
"filepath": "",
|
||||
"highlight": "",
|
||||
"num": "",
|
||||
"time": "",
|
||||
"uuid": "",
|
||||
"warning": "",
|
||||
"bar.back": "",
|
||||
"bar.complete": "",
|
||||
"bar.finished": "",
|
||||
"bar.pulse": "",
|
||||
"progress.elapsed": "",
|
||||
"progress.percentage": "",
|
||||
"progress.remaining": "",
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def get_theme(
|
||||
theme_name: Optional[str] = None,
|
||||
theme_file: Optional[str] = None,
|
||||
verbose=None,
|
||||
):
|
||||
"""Get the color theme based on the color flags or load from config file"""
|
||||
if not verbose:
|
||||
verbose = noop
|
||||
# figure out which color theme to use
|
||||
theme_name = theme_name or "default"
|
||||
if theme_name == "default" and theme_file and theme_file.is_file():
|
||||
# load theme from file
|
||||
verbose(f"Loading color theme from {theme_file}")
|
||||
try:
|
||||
theme = Theme.read(theme_file)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error reading theme file {theme_file}: {e}")
|
||||
elif theme_name == "default":
|
||||
# try to auto-detect dark/light mode
|
||||
theme = COLOR_THEMES["dark"] if is_dark_mode() else COLOR_THEMES["light"]
|
||||
else:
|
||||
theme = COLOR_THEMES[theme_name]
|
||||
return theme
|
||||
526
osxphotos/cli/common.py
Normal file
526
osxphotos/cli/common.py
Normal file
@@ -0,0 +1,526 @@
|
||||
"""Globals and constants use by the CLI commands"""
|
||||
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
from datetime import datetime
|
||||
|
||||
import click
|
||||
|
||||
import osxphotos
|
||||
from osxphotos._version import __version__
|
||||
|
||||
from .param_types import *
|
||||
|
||||
# used to show/hide hidden commands
|
||||
OSXPHOTOS_HIDDEN = not bool(os.getenv("OSXPHOTOS_SHOW_HIDDEN", default=False))
|
||||
|
||||
# used by snap and diff commands
|
||||
OSXPHOTOS_SNAPSHOT_DIR = "/private/tmp/osxphotos_snapshots"
|
||||
|
||||
# where to write the crash report if osxphotos crashes
|
||||
OSXPHOTOS_CRASH_LOG = f"{os.getcwd()}/osxphotos_crash.log"
|
||||
|
||||
CLI_COLOR_ERROR = "red"
|
||||
CLI_COLOR_WARNING = "yellow"
|
||||
|
||||
__all__ = [
|
||||
"CLI_COLOR_ERROR",
|
||||
"CLI_COLOR_WARNING",
|
||||
"DB_ARGUMENT",
|
||||
"DB_OPTION",
|
||||
"DEBUG_OPTIONS",
|
||||
"DELETED_OPTIONS",
|
||||
"JSON_OPTION",
|
||||
"QUERY_OPTIONS",
|
||||
"get_photos_db",
|
||||
"load_uuid_from_file",
|
||||
"noop",
|
||||
"time_stamp",
|
||||
]
|
||||
|
||||
|
||||
def noop(*args, **kwargs):
|
||||
"""no-op function"""
|
||||
pass
|
||||
|
||||
|
||||
def time_stamp() -> str:
|
||||
"""return timestamp"""
|
||||
return f"[time]{str(datetime.now())}[/time] -- "
|
||||
|
||||
|
||||
def get_photos_db(*db_options):
|
||||
"""Return path to photos db, select first non-None db_options
|
||||
If no db_options are non-None, try to find library to use in
|
||||
the following order:
|
||||
- last library opened
|
||||
- system library
|
||||
- ~/Pictures/Photos Library.photoslibrary
|
||||
- failing above, returns None
|
||||
"""
|
||||
if db_options:
|
||||
for db in db_options:
|
||||
if db is not None:
|
||||
return db
|
||||
|
||||
# if get here, no valid database paths passed, so try to figure out which to use
|
||||
db = osxphotos.utils.get_last_library_path()
|
||||
if db is not None:
|
||||
click.echo(f"Using last opened Photos library: {db}", err=True)
|
||||
return db
|
||||
|
||||
db = osxphotos.utils.get_system_library_path()
|
||||
if db is not None:
|
||||
click.echo(f"Using system Photos library: {db}", err=True)
|
||||
return db
|
||||
|
||||
db = os.path.expanduser("~/Pictures/Photos Library.photoslibrary")
|
||||
if os.path.isdir(db):
|
||||
click.echo(f"Using Photos library: {db}", err=True)
|
||||
return db
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
DB_OPTION = click.option(
|
||||
"--db",
|
||||
required=False,
|
||||
metavar="<Photos database path>",
|
||||
default=None,
|
||||
help=(
|
||||
"Specify Photos database path. "
|
||||
"Path to Photos library/database can be specified using either --db "
|
||||
"or directly as PHOTOS_LIBRARY positional argument. "
|
||||
"If neither --db or PHOTOS_LIBRARY provided, will attempt to find the library "
|
||||
"to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary"
|
||||
),
|
||||
type=click.Path(exists=True),
|
||||
)
|
||||
|
||||
DB_ARGUMENT = click.argument("photos_library", nargs=-1, type=click.Path(exists=True))
|
||||
|
||||
JSON_OPTION = click.option(
|
||||
"--json",
|
||||
"json_",
|
||||
required=False,
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Print output in JSON format.",
|
||||
)
|
||||
|
||||
|
||||
def DELETED_OPTIONS(f):
|
||||
o = click.option
|
||||
options = [
|
||||
o(
|
||||
"--deleted",
|
||||
is_flag=True,
|
||||
help="Include photos from the 'Recently Deleted' folder.",
|
||||
),
|
||||
o(
|
||||
"--deleted-only",
|
||||
is_flag=True,
|
||||
help="Include only photos from the 'Recently Deleted' folder.",
|
||||
),
|
||||
]
|
||||
for o in options[::-1]:
|
||||
f = o(f)
|
||||
return f
|
||||
|
||||
|
||||
def QUERY_OPTIONS(f):
|
||||
o = click.option
|
||||
options = [
|
||||
o(
|
||||
"--keyword",
|
||||
metavar="KEYWORD",
|
||||
default=None,
|
||||
multiple=True,
|
||||
help="Search for photos with keyword KEYWORD. "
|
||||
'If more than one keyword, treated as "OR", e.g. find photos matching any keyword',
|
||||
),
|
||||
o(
|
||||
"--person",
|
||||
metavar="PERSON",
|
||||
default=None,
|
||||
multiple=True,
|
||||
help="Search for photos with person PERSON. "
|
||||
'If more than one person, treated as "OR", e.g. find photos matching any person',
|
||||
),
|
||||
o(
|
||||
"--album",
|
||||
metavar="ALBUM",
|
||||
default=None,
|
||||
multiple=True,
|
||||
help="Search for photos in album ALBUM. "
|
||||
'If more than one album, treated as "OR", e.g. find photos matching any album',
|
||||
),
|
||||
o(
|
||||
"--folder",
|
||||
metavar="FOLDER",
|
||||
default=None,
|
||||
multiple=True,
|
||||
help="Search for photos in an album in folder FOLDER. "
|
||||
'If more than one folder, treated as "OR", e.g. find photos in any FOLDER. '
|
||||
"Only searches top level folders (e.g. does not look at subfolders)",
|
||||
),
|
||||
o(
|
||||
"--name",
|
||||
metavar="FILENAME",
|
||||
default=None,
|
||||
multiple=True,
|
||||
help="Search for photos with filename matching FILENAME. "
|
||||
'If more than one --name options is specified, they are treated as "OR", '
|
||||
"e.g. find photos matching any FILENAME. ",
|
||||
),
|
||||
o(
|
||||
"--uuid",
|
||||
metavar="UUID",
|
||||
default=None,
|
||||
multiple=True,
|
||||
help="Search for photos with UUID(s). "
|
||||
"May be repeated to include multiple UUIDs.",
|
||||
),
|
||||
o(
|
||||
"--uuid-from-file",
|
||||
metavar="FILE",
|
||||
default=None,
|
||||
multiple=False,
|
||||
help="Search for photos with UUID(s) loaded from FILE. "
|
||||
"Format is a single UUID per line. Lines preceded with # are ignored.",
|
||||
type=click.Path(exists=True),
|
||||
),
|
||||
o(
|
||||
"--title",
|
||||
metavar="TITLE",
|
||||
default=None,
|
||||
multiple=True,
|
||||
help="Search for TITLE in title of photo.",
|
||||
),
|
||||
o("--no-title", is_flag=True, help="Search for photos with no title."),
|
||||
o(
|
||||
"--description",
|
||||
metavar="DESC",
|
||||
default=None,
|
||||
multiple=True,
|
||||
help="Search for DESC in description of photo.",
|
||||
),
|
||||
o(
|
||||
"--no-description",
|
||||
is_flag=True,
|
||||
help="Search for photos with no description.",
|
||||
),
|
||||
o(
|
||||
"--place",
|
||||
metavar="PLACE",
|
||||
default=None,
|
||||
multiple=True,
|
||||
help="Search for PLACE in photo's reverse geolocation info",
|
||||
),
|
||||
o(
|
||||
"--no-place",
|
||||
is_flag=True,
|
||||
help="Search for photos with no associated place name info (no reverse geolocation info)",
|
||||
),
|
||||
o(
|
||||
"--location",
|
||||
is_flag=True,
|
||||
help="Search for photos with associated location info (e.g. GPS coordinates)",
|
||||
),
|
||||
o(
|
||||
"--no-location",
|
||||
is_flag=True,
|
||||
help="Search for photos with no associated location info (e.g. no GPS coordinates)",
|
||||
),
|
||||
o(
|
||||
"--label",
|
||||
metavar="LABEL",
|
||||
multiple=True,
|
||||
help="Search for photos with image classification label LABEL (Photos 5 only). "
|
||||
'If more than one label, treated as "OR", e.g. find photos matching any label',
|
||||
),
|
||||
o(
|
||||
"--uti",
|
||||
metavar="UTI",
|
||||
default=None,
|
||||
multiple=False,
|
||||
help="Search for photos whose uniform type identifier (UTI) matches UTI",
|
||||
),
|
||||
o(
|
||||
"-i",
|
||||
"--ignore-case",
|
||||
is_flag=True,
|
||||
help="Case insensitive search for title, description, place, keyword, person, or album.",
|
||||
),
|
||||
o("--edited", is_flag=True, help="Search for photos that have been edited."),
|
||||
o(
|
||||
"--external-edit",
|
||||
is_flag=True,
|
||||
help="Search for photos edited in external editor.",
|
||||
),
|
||||
o("--favorite", is_flag=True, help="Search for photos marked favorite."),
|
||||
o(
|
||||
"--not-favorite",
|
||||
is_flag=True,
|
||||
help="Search for photos not marked favorite.",
|
||||
),
|
||||
o("--hidden", is_flag=True, help="Search for photos marked hidden."),
|
||||
o("--not-hidden", is_flag=True, help="Search for photos not marked hidden."),
|
||||
o(
|
||||
"--shared",
|
||||
is_flag=True,
|
||||
help="Search for photos in shared iCloud album (Photos 5 only).",
|
||||
),
|
||||
o(
|
||||
"--not-shared",
|
||||
is_flag=True,
|
||||
help="Search for photos not in shared iCloud album (Photos 5 only).",
|
||||
),
|
||||
o(
|
||||
"--burst",
|
||||
is_flag=True,
|
||||
help="Search for photos that were taken in a burst.",
|
||||
),
|
||||
o(
|
||||
"--not-burst",
|
||||
is_flag=True,
|
||||
help="Search for photos that are not part of a burst.",
|
||||
),
|
||||
o("--live", is_flag=True, help="Search for Apple live photos"),
|
||||
o(
|
||||
"--not-live",
|
||||
is_flag=True,
|
||||
help="Search for photos that are not Apple live photos.",
|
||||
),
|
||||
o("--portrait", is_flag=True, help="Search for Apple portrait mode photos."),
|
||||
o(
|
||||
"--not-portrait",
|
||||
is_flag=True,
|
||||
help="Search for photos that are not Apple portrait mode photos.",
|
||||
),
|
||||
o("--screenshot", is_flag=True, help="Search for screenshot photos."),
|
||||
o(
|
||||
"--not-screenshot",
|
||||
is_flag=True,
|
||||
help="Search for photos that are not screenshot photos.",
|
||||
),
|
||||
o("--slow-mo", is_flag=True, help="Search for slow motion videos."),
|
||||
o(
|
||||
"--not-slow-mo",
|
||||
is_flag=True,
|
||||
help="Search for photos that are not slow motion videos.",
|
||||
),
|
||||
o("--time-lapse", is_flag=True, help="Search for time lapse videos."),
|
||||
o(
|
||||
"--not-time-lapse",
|
||||
is_flag=True,
|
||||
help="Search for photos that are not time lapse videos.",
|
||||
),
|
||||
o("--hdr", is_flag=True, help="Search for high dynamic range (HDR) photos."),
|
||||
o("--not-hdr", is_flag=True, help="Search for photos that are not HDR photos."),
|
||||
o(
|
||||
"--selfie",
|
||||
is_flag=True,
|
||||
help="Search for selfies (photos taken with front-facing cameras).",
|
||||
),
|
||||
o("--not-selfie", is_flag=True, help="Search for photos that are not selfies."),
|
||||
o("--panorama", is_flag=True, help="Search for panorama photos."),
|
||||
o(
|
||||
"--not-panorama",
|
||||
is_flag=True,
|
||||
help="Search for photos that are not panoramas.",
|
||||
),
|
||||
o(
|
||||
"--has-raw",
|
||||
is_flag=True,
|
||||
help="Search for photos with both a jpeg and raw version",
|
||||
),
|
||||
o(
|
||||
"--only-movies",
|
||||
is_flag=True,
|
||||
help="Search only for movies (default searches both images and movies).",
|
||||
),
|
||||
o(
|
||||
"--only-photos",
|
||||
is_flag=True,
|
||||
help="Search only for photos/images (default searches both images and movies).",
|
||||
),
|
||||
o(
|
||||
"--from-date",
|
||||
help="Search by item start date, e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).",
|
||||
type=DateTimeISO8601(),
|
||||
),
|
||||
o(
|
||||
"--to-date",
|
||||
help="Search by item end date, e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).",
|
||||
type=DateTimeISO8601(),
|
||||
),
|
||||
o(
|
||||
"--from-time",
|
||||
help="Search by item start time of day, e.g. 12:00, or 12:00:00.",
|
||||
type=TimeISO8601(),
|
||||
),
|
||||
o(
|
||||
"--to-time",
|
||||
help="Search by item end time of day, e.g. 12:00 or 12:00:00.",
|
||||
type=TimeISO8601(),
|
||||
),
|
||||
o("--has-comment", is_flag=True, help="Search for photos that have comments."),
|
||||
o("--no-comment", is_flag=True, help="Search for photos with no comments."),
|
||||
o("--has-likes", is_flag=True, help="Search for photos that have likes."),
|
||||
o("--no-likes", is_flag=True, help="Search for photos with no likes."),
|
||||
o(
|
||||
"--is-reference",
|
||||
is_flag=True,
|
||||
help="Search for photos that were imported as referenced files (not copied into Photos library).",
|
||||
),
|
||||
o(
|
||||
"--in-album",
|
||||
is_flag=True,
|
||||
help="Search for photos that are in one or more albums.",
|
||||
),
|
||||
o(
|
||||
"--not-in-album",
|
||||
is_flag=True,
|
||||
help="Search for photos that are not in any albums.",
|
||||
),
|
||||
o(
|
||||
"--duplicate",
|
||||
is_flag=True,
|
||||
help="Search for photos with possible duplicates. osxphotos will compare signatures of photos, "
|
||||
"evaluating date created, size, height, width, and edited status to find *possible* duplicates. "
|
||||
"This does not compare images byte-for-byte nor compare hashes but should find photos imported multiple "
|
||||
"times or duplicated within Photos.",
|
||||
),
|
||||
o(
|
||||
"--min-size",
|
||||
metavar="SIZE",
|
||||
type=BitMathSize(),
|
||||
help="Search for photos with size >= SIZE bytes. "
|
||||
"The size evaluated is the photo's original size (when imported to Photos). "
|
||||
"Size may be specified as integer bytes or using SI or NIST units. "
|
||||
"For example, the following are all valid and equivalent sizes: '1048576' '1.048576MB', '1 MiB'.",
|
||||
),
|
||||
o(
|
||||
"--max-size",
|
||||
metavar="SIZE",
|
||||
type=BitMathSize(),
|
||||
help="Search for photos with size <= SIZE bytes. "
|
||||
"The size evaluated is the photo's original size (when imported to Photos). "
|
||||
"Size may be specified as integer bytes or using SI or NIST units. "
|
||||
"For example, the following are all valid and equivalent sizes: '1048576' '1.048576MB', '1 MiB'.",
|
||||
),
|
||||
o(
|
||||
"--regex",
|
||||
metavar="REGEX TEMPLATE",
|
||||
nargs=2,
|
||||
multiple=True,
|
||||
help="Search for photos where TEMPLATE matches regular expression REGEX. "
|
||||
"For example, to find photos in an album that begins with 'Beach': '--regex \"^Beach\" \"{album}\"'. "
|
||||
"You may specify more than one regular expression match by repeating '--regex' with different arguments.",
|
||||
),
|
||||
o(
|
||||
"--selected",
|
||||
is_flag=True,
|
||||
help="Filter for photos that are currently selected in Photos.",
|
||||
),
|
||||
o(
|
||||
"--exif",
|
||||
metavar="EXIF_TAG VALUE",
|
||||
nargs=2,
|
||||
multiple=True,
|
||||
help="Search for photos where EXIF_TAG exists in photo's EXIF data and contains VALUE. "
|
||||
"For example, to find photos created by Adobe Photoshop: `--exif Software 'Adobe Photoshop' `"
|
||||
"or to find all photos shot on a Canon camera: `--exif Make Canon`. "
|
||||
"EXIF_TAG can be any valid exiftool tag, with or without group name, e.g. `EXIF:Make` or `Make`. "
|
||||
"To use --exif, exiftool must be installed and in the path.",
|
||||
),
|
||||
o(
|
||||
"--query-eval",
|
||||
metavar="CRITERIA",
|
||||
multiple=True,
|
||||
help="Evaluate CRITERIA to filter photos. "
|
||||
"CRITERIA will be evaluated in context of the following python list comprehension: "
|
||||
"`photos = [photo for photo in photos if CRITERIA]` "
|
||||
"where photo represents a PhotoInfo object. "
|
||||
"For example: `--query-eval photo.favorite` returns all photos that have been "
|
||||
"favorited and is equivalent to --favorite. "
|
||||
"You may specify more than one CRITERIA by using --query-eval multiple times. "
|
||||
"CRITERIA must be a valid python expression. "
|
||||
"See https://rhettbull.github.io/osxphotos/ for additional documentation on the PhotoInfo class.",
|
||||
),
|
||||
o(
|
||||
"--query-function",
|
||||
metavar="filename.py::function",
|
||||
multiple=True,
|
||||
type=FunctionCall(),
|
||||
help="Run function to filter photos. Use this in format: --query-function filename.py::function where filename.py is a python "
|
||||
+ "file you've created and function is the name of the function in the python file you want to call. "
|
||||
+ "Your function will be passed a list of PhotoInfo objects and is expected to return a filtered list of PhotoInfo objects. "
|
||||
+ "You may use more than one function by repeating the --query-function option with a different value. "
|
||||
+ "Your query function will be called after all other query options have been evaluated. "
|
||||
+ "See https://github.com/RhetTbull/osxphotos/blob/master/examples/query_function.py for example of how to use this option.",
|
||||
),
|
||||
]
|
||||
for o in options[::-1]:
|
||||
f = o(f)
|
||||
return f
|
||||
|
||||
|
||||
def DEBUG_OPTIONS(f):
|
||||
o = click.option
|
||||
options = [
|
||||
o(
|
||||
"--debug",
|
||||
is_flag=True,
|
||||
help="Enable debug output.",
|
||||
hidden=OSXPHOTOS_HIDDEN,
|
||||
),
|
||||
o(
|
||||
"--watch",
|
||||
metavar="FUNCTION_PATH",
|
||||
multiple=True,
|
||||
help="Watch function calls. For example, to watch all calls to FileUtil.copy: "
|
||||
"'--watch osxphotos.fileutil.FileUtil.copy'. More than one --watch option can be specified.",
|
||||
hidden=OSXPHOTOS_HIDDEN,
|
||||
),
|
||||
o(
|
||||
"--breakpoint",
|
||||
metavar="FUNCTION_PATH",
|
||||
multiple=True,
|
||||
help="Add breakpoint to function calls. For example, to add breakpoint to FileUtil.copy: "
|
||||
"'--breakpoint osxphotos.fileutil.FileUtil.copy'. More than one --breakpoint option can be specified.",
|
||||
hidden=OSXPHOTOS_HIDDEN,
|
||||
),
|
||||
]
|
||||
for o in options[::-1]:
|
||||
f = o(f)
|
||||
return f
|
||||
|
||||
|
||||
def load_uuid_from_file(filename):
|
||||
"""Load UUIDs from file. Does not validate UUIDs.
|
||||
Format is 1 UUID per line, any line beginning with # is ignored.
|
||||
Whitespace is stripped.
|
||||
|
||||
Arguments:
|
||||
filename: file name of the file containing UUIDs
|
||||
|
||||
Returns:
|
||||
list of UUIDs or empty list of no UUIDs in file
|
||||
|
||||
Raises:
|
||||
FileNotFoundError if file does not exist
|
||||
"""
|
||||
|
||||
if not pathlib.Path(filename).is_file():
|
||||
raise FileNotFoundError(f"Could not find file {filename}")
|
||||
|
||||
uuid = []
|
||||
with open(filename, "r") as uuid_file:
|
||||
for line in uuid_file:
|
||||
line = line.strip()
|
||||
if len(line) and line[0] != "#":
|
||||
uuid.append(line)
|
||||
return uuid
|
||||
19
osxphotos/cli/darkmode.py
Normal file
19
osxphotos/cli/darkmode.py
Normal file
@@ -0,0 +1,19 @@
|
||||
"""Detect dark mode on MacOS >= 10.14"""
|
||||
|
||||
import objc
|
||||
import Foundation
|
||||
|
||||
|
||||
def theme():
|
||||
with objc.autorelease_pool():
|
||||
user_defaults = Foundation.NSUserDefaults.standardUserDefaults()
|
||||
system_theme = user_defaults.stringForKey_("AppleInterfaceStyle")
|
||||
return "dark" if system_theme == "Dark" else "light"
|
||||
|
||||
|
||||
def is_dark_mode():
|
||||
return theme() == "dark"
|
||||
|
||||
|
||||
def is_light_mode():
|
||||
return theme() == "light"
|
||||
97
osxphotos/cli/debug_dump.py
Normal file
97
osxphotos/cli/debug_dump.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""debug-dump command for osxphotos CLI"""
|
||||
|
||||
import pprint
|
||||
import time
|
||||
|
||||
import click
|
||||
from rich import print
|
||||
|
||||
import osxphotos
|
||||
from osxphotos._constants import _PHOTOS_4_VERSION, _UNKNOWN_PLACE
|
||||
|
||||
from .common import DB_ARGUMENT, DB_OPTION, JSON_OPTION, OSXPHOTOS_HIDDEN, get_photos_db
|
||||
from .list import _list_libraries
|
||||
from .verbose import verbose_print
|
||||
|
||||
|
||||
@click.command(hidden=OSXPHOTOS_HIDDEN)
|
||||
@DB_OPTION
|
||||
@DB_ARGUMENT
|
||||
@click.option(
|
||||
"--dump",
|
||||
metavar="ATTR",
|
||||
help="Name of PhotosDB attribute to print; "
|
||||
+ "can also use albums, persons, keywords, photos to dump related attributes.",
|
||||
multiple=True,
|
||||
)
|
||||
@click.option(
|
||||
"--uuid",
|
||||
metavar="UUID",
|
||||
help="Use with '--dump photos' to dump only certain UUIDs. "
|
||||
"May be repeated to include multiple UUIDs.",
|
||||
multiple=True,
|
||||
)
|
||||
@click.option("--verbose", "-V", "verbose", is_flag=True, help="Print verbose output.")
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
def debug_dump(ctx, cli_obj, db, photos_library, dump, uuid, verbose):
|
||||
"""Print out debug info"""
|
||||
|
||||
verbose_ = verbose_print(verbose, rich=True)
|
||||
db = get_photos_db(*photos_library, db, cli_obj.db)
|
||||
if db is None:
|
||||
click.echo(ctx.obj.group.commands["debug-dump"].get_help(ctx), err=True)
|
||||
click.echo("\n\nLocated the following Photos library databases: ", err=True)
|
||||
_list_libraries()
|
||||
return
|
||||
|
||||
start_t = time.perf_counter()
|
||||
print(f"Opening database: {db}")
|
||||
photosdb = osxphotos.PhotosDB(dbfile=db, verbose=verbose_)
|
||||
stop_t = time.perf_counter()
|
||||
print(f"Done; took {(stop_t-start_t):.2f} seconds")
|
||||
|
||||
for attr in dump:
|
||||
if attr == "albums":
|
||||
print("_dbalbums_album:")
|
||||
pprint.pprint(photosdb._dbalbums_album)
|
||||
print("_dbalbums_uuid:")
|
||||
pprint.pprint(photosdb._dbalbums_uuid)
|
||||
print("_dbalbum_details:")
|
||||
pprint.pprint(photosdb._dbalbum_details)
|
||||
print("_dbalbum_folders:")
|
||||
pprint.pprint(photosdb._dbalbum_folders)
|
||||
print("_dbfolder_details:")
|
||||
pprint.pprint(photosdb._dbfolder_details)
|
||||
elif attr == "keywords":
|
||||
print("_dbkeywords_keyword:")
|
||||
pprint.pprint(photosdb._dbkeywords_keyword)
|
||||
print("_dbkeywords_uuid:")
|
||||
pprint.pprint(photosdb._dbkeywords_uuid)
|
||||
elif attr == "persons":
|
||||
print("_dbfaces_uuid:")
|
||||
pprint.pprint(photosdb._dbfaces_uuid)
|
||||
print("_dbfaces_pk:")
|
||||
pprint.pprint(photosdb._dbfaces_pk)
|
||||
print("_dbpersons_pk:")
|
||||
pprint.pprint(photosdb._dbpersons_pk)
|
||||
print("_dbpersons_fullname:")
|
||||
pprint.pprint(photosdb._dbpersons_fullname)
|
||||
elif attr == "photos":
|
||||
if uuid:
|
||||
for uuid_ in uuid:
|
||||
print(f"_dbphotos['{uuid_}']:")
|
||||
try:
|
||||
pprint.pprint(photosdb._dbphotos[uuid_])
|
||||
except KeyError:
|
||||
print(f"Did not find uuid {uuid_} in _dbphotos")
|
||||
else:
|
||||
print("_dbphotos:")
|
||||
pprint.pprint(photosdb._dbphotos)
|
||||
else:
|
||||
try:
|
||||
val = getattr(photosdb, attr)
|
||||
print(f"{attr}:")
|
||||
pprint.pprint(val)
|
||||
except Exception:
|
||||
print(f"Did not find attribute {attr} in PhotosDB")
|
||||
44
osxphotos/cli/dump.py
Normal file
44
osxphotos/cli/dump.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""dump command for osxphotos CLI """
|
||||
|
||||
import click
|
||||
|
||||
import osxphotos
|
||||
from osxphotos.queryoptions import QueryOptions
|
||||
|
||||
from .common import DB_ARGUMENT, DB_OPTION, DELETED_OPTIONS, JSON_OPTION, get_photos_db
|
||||
from .list import _list_libraries
|
||||
from .print_photo_info import print_photo_info
|
||||
|
||||
|
||||
@click.command()
|
||||
@DB_OPTION
|
||||
@JSON_OPTION
|
||||
@DELETED_OPTIONS
|
||||
@DB_ARGUMENT
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
def dump(ctx, cli_obj, db, json_, deleted, deleted_only, photos_library):
|
||||
"""Print list of all photos & associated info from the Photos library."""
|
||||
|
||||
db = get_photos_db(*photos_library, db, cli_obj.db)
|
||||
if db is None:
|
||||
click.echo(ctx.obj.group.commands["dump"].get_help(ctx), err=True)
|
||||
click.echo("\n\nLocated the following Photos library databases: ", err=True)
|
||||
_list_libraries()
|
||||
return
|
||||
|
||||
# check exclusive options
|
||||
if deleted and deleted_only:
|
||||
click.echo("Incompatible dump options", err=True)
|
||||
click.echo(ctx.obj.group.commands["dump"].get_help(ctx), err=True)
|
||||
return
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=db)
|
||||
if deleted or deleted_only:
|
||||
photos = photosdb.photos(movies=True, intrash=True)
|
||||
else:
|
||||
photos = []
|
||||
if not deleted_only:
|
||||
photos += photosdb.photos(movies=True)
|
||||
|
||||
print_photo_info(photos, json_ or cli_obj.json)
|
||||
File diff suppressed because it is too large
Load Diff
252
osxphotos/cli/exportdb.py
Normal file
252
osxphotos/cli/exportdb.py
Normal file
@@ -0,0 +1,252 @@
|
||||
"""exportdb command for osxphotos CLI"""
|
||||
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
import click
|
||||
from rich import print
|
||||
|
||||
from osxphotos._constants import OSXPHOTOS_EXPORT_DB
|
||||
from osxphotos._version import __version__
|
||||
from osxphotos.export_db import OSXPHOTOS_EXPORTDB_VERSION, ExportDB
|
||||
from osxphotos.export_db_utils import (
|
||||
export_db_check_signatures,
|
||||
export_db_get_last_run,
|
||||
export_db_get_version,
|
||||
export_db_save_config_to_file,
|
||||
export_db_touch_files,
|
||||
export_db_update_signatures,
|
||||
export_db_vacuum,
|
||||
)
|
||||
|
||||
from .common import OSXPHOTOS_HIDDEN
|
||||
from .verbose import verbose_print
|
||||
|
||||
|
||||
@click.command(name="exportdb", hidden=OSXPHOTOS_HIDDEN)
|
||||
@click.option("--version", is_flag=True, help="Print export database version and exit.")
|
||||
@click.option("--vacuum", is_flag=True, help="Run VACUUM to defragment the database.")
|
||||
@click.option(
|
||||
"--check-signatures",
|
||||
is_flag=True,
|
||||
help="Check signatures for all exported photos in the database to find signatures that don't match.",
|
||||
)
|
||||
@click.option(
|
||||
"--update-signatures",
|
||||
is_flag=True,
|
||||
help="Update signatures for all exported photos in the database to match on-disk signatures.",
|
||||
)
|
||||
@click.option(
|
||||
"--touch-file",
|
||||
is_flag=True,
|
||||
help="Touch files on disk to match created date in Photos library and update export database signatures",
|
||||
)
|
||||
@click.option(
|
||||
"--last-run",
|
||||
is_flag=True,
|
||||
help="Show last run osxphotos commands used with this database.",
|
||||
)
|
||||
@click.option(
|
||||
"--save-config",
|
||||
metavar="CONFIG_FILE",
|
||||
help="Save last run configuration to TOML file for use by --load-config.",
|
||||
)
|
||||
@click.option(
|
||||
"--info",
|
||||
metavar="FILE_PATH",
|
||||
nargs=1,
|
||||
help="Print information about FILE_PATH contained in the database.",
|
||||
)
|
||||
@click.option(
|
||||
"--migrate",
|
||||
is_flag=True,
|
||||
help="Migrate (if needed) export database to current version.",
|
||||
)
|
||||
@click.option(
|
||||
"--sql",
|
||||
metavar="SQL_STATEMENT",
|
||||
help="Execute SQL_STATEMENT against export database and print results.",
|
||||
)
|
||||
@click.option(
|
||||
"--export-dir",
|
||||
help="Optional path to export directory (if not parent of export database).",
|
||||
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
||||
)
|
||||
@click.option("--verbose", "-V", is_flag=True, help="Print verbose output.")
|
||||
@click.option(
|
||||
"--dry-run",
|
||||
is_flag=True,
|
||||
help="Run in dry-run mode (don't actually update files), e.g. for use with --update-signatures.",
|
||||
)
|
||||
@click.argument("export_db", metavar="EXPORT_DATABASE", type=click.Path(exists=True))
|
||||
def exportdb(
|
||||
version,
|
||||
vacuum,
|
||||
check_signatures,
|
||||
update_signatures,
|
||||
touch_file,
|
||||
last_run,
|
||||
save_config,
|
||||
info,
|
||||
migrate,
|
||||
sql,
|
||||
export_dir,
|
||||
verbose,
|
||||
dry_run,
|
||||
export_db,
|
||||
):
|
||||
"""Utilities for working with the osxphotos export database"""
|
||||
|
||||
verbose_ = verbose_print(verbose, rich=True)
|
||||
|
||||
export_db = pathlib.Path(export_db)
|
||||
if export_db.is_dir():
|
||||
# assume it's the export folder
|
||||
export_db = export_db / OSXPHOTOS_EXPORT_DB
|
||||
if not export_db.is_file():
|
||||
print(
|
||||
f"[red]Error: {OSXPHOTOS_EXPORT_DB} missing from {export_db.parent}[/red]"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
export_dir = export_dir or export_db.parent
|
||||
|
||||
sub_commands = [
|
||||
version,
|
||||
check_signatures,
|
||||
update_signatures,
|
||||
touch_file,
|
||||
last_run,
|
||||
bool(save_config),
|
||||
bool(info),
|
||||
migrate,
|
||||
bool(sql),
|
||||
]
|
||||
if sum(sub_commands) > 1:
|
||||
print("[red]Only a single sub-command may be specified at a time[/red]")
|
||||
sys.exit(1)
|
||||
|
||||
if version:
|
||||
try:
|
||||
osxphotos_ver, export_db_ver = export_db_get_version(export_db)
|
||||
except Exception as e:
|
||||
print(f"[red]Error: could not read version from {export_db}: {e}[/red]")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(
|
||||
f"osxphotos version: {osxphotos_ver}, export database version: {export_db_ver}"
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
if vacuum:
|
||||
try:
|
||||
start_size = pathlib.Path(export_db).stat().st_size
|
||||
export_db_vacuum(export_db)
|
||||
except Exception as e:
|
||||
print(f"[red]Error: {e}[/red]")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(
|
||||
f"Vacuumed {export_db}! {start_size} bytes -> {pathlib.Path(export_db).stat().st_size} bytes"
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
if update_signatures:
|
||||
try:
|
||||
updated, skipped = export_db_update_signatures(
|
||||
export_db, export_dir, verbose_, dry_run
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[red]Error: {e}[/red]")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"Done. Updated {updated} files, skipped {skipped} files.")
|
||||
sys.exit(0)
|
||||
|
||||
if last_run:
|
||||
try:
|
||||
last_run_info = export_db_get_last_run(export_db)
|
||||
except Exception as e:
|
||||
print(f"[red]Error: {e}[/red]")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"last run at {last_run_info[0]}:")
|
||||
print(f"osxphotos {last_run_info[1]}")
|
||||
sys.exit(0)
|
||||
|
||||
if save_config:
|
||||
try:
|
||||
export_db_save_config_to_file(export_db, save_config)
|
||||
except Exception as e:
|
||||
print(f"[red]Error: {e}[/red]")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"Saved configuration to {save_config}")
|
||||
sys.exit(0)
|
||||
|
||||
if check_signatures:
|
||||
try:
|
||||
matched, notmatched, skipped = export_db_check_signatures(
|
||||
export_db, export_dir, verbose_=verbose_
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[red]Error: {e}[/red]")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(
|
||||
f"Done. Found {matched} matching signatures and {notmatched} signatures that don't match. Skipped {skipped} missing files."
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
if touch_file:
|
||||
try:
|
||||
touched, not_touched, skipped = export_db_touch_files(
|
||||
export_db, export_dir, verbose_=verbose_, dry_run=dry_run
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[red]Error: {e}[/red]")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(
|
||||
f"Done. Touched {touched} files, skipped {not_touched} up to date files, skipped {skipped} missing files."
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
if info:
|
||||
exportdb = ExportDB(export_db, export_dir)
|
||||
try:
|
||||
info_rec = exportdb.get_file_record(info)
|
||||
except Exception as e:
|
||||
print(f"[red]Error: {e}[/red]")
|
||||
sys.exit(1)
|
||||
else:
|
||||
if info_rec:
|
||||
print(info_rec.asdict())
|
||||
else:
|
||||
print(f"[red]File '{info}' not found in export database[/red]")
|
||||
sys.exit(0)
|
||||
|
||||
if migrate:
|
||||
exportdb = ExportDB(export_db, export_dir)
|
||||
if upgraded := exportdb.was_upgraded:
|
||||
print(
|
||||
f"Migrated export database {export_db} from version {upgraded[0]} to {upgraded[1]}"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"Export database {export_db} is already at latest version {OSXPHOTOS_EXPORTDB_VERSION}"
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
if sql:
|
||||
exportdb = ExportDB(export_db, export_dir)
|
||||
try:
|
||||
c = exportdb._conn.cursor()
|
||||
results = c.execute(sql)
|
||||
except Exception as e:
|
||||
print(f"[red]Error: {e}[/red]")
|
||||
sys.exit(1)
|
||||
else:
|
||||
for row in results:
|
||||
print(row)
|
||||
sys.exit(0)
|
||||
57
osxphotos/cli/grep.py
Normal file
57
osxphotos/cli/grep.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""grep command for osxphotos CLI """
|
||||
|
||||
import pathlib
|
||||
|
||||
import click
|
||||
from rich import print
|
||||
|
||||
from osxphotos.photosdb.photosdb_utils import get_photos_library_version
|
||||
from osxphotos.sqlgrep import sqlgrep
|
||||
|
||||
from .common import DB_OPTION, OSXPHOTOS_HIDDEN, get_photos_db
|
||||
|
||||
|
||||
@click.command(name="grep", hidden=OSXPHOTOS_HIDDEN)
|
||||
@DB_OPTION
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
@click.option(
|
||||
"--ignore-case",
|
||||
"-i",
|
||||
required=False,
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Ignore case when searching (default is case-sensitive).",
|
||||
)
|
||||
@click.option(
|
||||
"--print-filename",
|
||||
"-p",
|
||||
required=False,
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Print name of database file when printing results.",
|
||||
)
|
||||
@click.argument("pattern", metavar="PATTERN", required=True)
|
||||
def grep(ctx, cli_obj, db, ignore_case, print_filename, pattern):
|
||||
"""Search for PATTERN in the Photos sqlite database file"""
|
||||
db = db or get_photos_db()
|
||||
db = pathlib.Path(db)
|
||||
if db.is_file():
|
||||
# if passed the actual database, really want the parent of the database directory
|
||||
db = db.parent.parent
|
||||
photos_ver = get_photos_library_version(str(db))
|
||||
if photos_ver < 5:
|
||||
db_file = db / "database" / "photos.db"
|
||||
else:
|
||||
db_file = db / "database" / "Photos.sqlite"
|
||||
|
||||
if not db_file.is_file():
|
||||
click.secho(f"Could not find database file {db_file}", fg="red")
|
||||
ctx.exit(2)
|
||||
|
||||
db_file = str(db_file)
|
||||
|
||||
for table, column, row_id, value in sqlgrep(
|
||||
db_file, pattern, ignore_case, print_filename, rich_markup=True
|
||||
):
|
||||
print(", ".join([table, column, row_id, value]))
|
||||
@@ -1,38 +1,180 @@
|
||||
"""Help text helper class for osxphotos CLI """
|
||||
|
||||
import inspect
|
||||
import io
|
||||
import pathlib
|
||||
import re
|
||||
import typing as t
|
||||
|
||||
import click
|
||||
import osxmetadata
|
||||
from rich.console import Console
|
||||
from rich.markdown import Markdown
|
||||
|
||||
from ._constants import (
|
||||
from osxphotos._constants import (
|
||||
EXTENDED_ATTRIBUTE_NAMES,
|
||||
EXTENDED_ATTRIBUTE_NAMES_QUOTED,
|
||||
OSXPHOTOS_EXPORT_DB,
|
||||
POST_COMMAND_CATEGORIES,
|
||||
)
|
||||
from .phototemplate import (
|
||||
from osxphotos.phototemplate import (
|
||||
TEMPLATE_SUBSTITUTIONS,
|
||||
TEMPLATE_SUBSTITUTIONS_MULTI_VALUED,
|
||||
TEMPLATE_SUBSTITUTIONS_PATHLIB,
|
||||
get_template_help,
|
||||
)
|
||||
|
||||
from .click_rich_echo import rich_echo
|
||||
from .color_themes import get_theme
|
||||
|
||||
__all__ = [
|
||||
"ExportCommand",
|
||||
"template_help",
|
||||
"tutorial_help",
|
||||
"rich_text",
|
||||
"strip_md_header_and_links",
|
||||
"strip_md_links",
|
||||
"strip_html_comments",
|
||||
"get_tutorial_text",
|
||||
"help",
|
||||
"get_help_msg",
|
||||
]
|
||||
|
||||
HIGHLIGHT_COLOR = "yellow"
|
||||
|
||||
|
||||
def get_help_msg(command):
|
||||
"""get help message for a Click command"""
|
||||
with click.Context(command) as ctx:
|
||||
return command.get_help(ctx)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("topic", default=None, required=False, nargs=1)
|
||||
@click.argument("subtopic", default=None, required=False, nargs=1)
|
||||
@click.pass_context
|
||||
def help(ctx, topic, subtopic, **kw):
|
||||
"""Print help; for help on commands: help <command>."""
|
||||
if topic is None:
|
||||
click.echo(ctx.parent.get_help())
|
||||
return
|
||||
|
||||
if subtopic:
|
||||
cmd = ctx.obj.group.commands[topic]
|
||||
theme = get_theme("light")
|
||||
rich_echo(
|
||||
get_subtopic_help(cmd, ctx, subtopic),
|
||||
theme=theme,
|
||||
width=click.HelpFormatter().width,
|
||||
)
|
||||
return
|
||||
|
||||
if topic in ctx.obj.group.commands:
|
||||
ctx.info_name = topic
|
||||
click.echo_via_pager(ctx.obj.group.commands[topic].get_help(ctx))
|
||||
return
|
||||
|
||||
# didn't find any valid help topics
|
||||
click.echo(f"Invalid command: {topic}", err=True)
|
||||
click.echo(ctx.parent.get_help())
|
||||
|
||||
|
||||
def get_subtopic_help(cmd: click.Command, ctx: click.Context, subtopic: str):
|
||||
"""Get help for a command including only options that match a subtopic"""
|
||||
|
||||
# set ctx.info_name or click prints the wrong usage str (usage for help instead of cmd)
|
||||
ctx.info_name = cmd.name
|
||||
usage_str = cmd.get_help(ctx)
|
||||
usage_str = usage_str.partition("\n")[0]
|
||||
|
||||
info = cmd.to_info_dict(ctx)
|
||||
help_str = info.get("help", "")
|
||||
|
||||
options = get_matching_options(cmd, ctx, subtopic)
|
||||
|
||||
# format help text and options
|
||||
formatter = click.HelpFormatter()
|
||||
formatter.write(usage_str)
|
||||
formatter.write_paragraph()
|
||||
format_help_text(help_str, formatter)
|
||||
formatter.write_paragraph()
|
||||
if options:
|
||||
option_str = format_options_help(options, ctx, highlight=subtopic)
|
||||
formatter.write(f"Options that match '[highlight]{subtopic}[/highlight]':\n")
|
||||
formatter.write_paragraph()
|
||||
formatter.write(option_str)
|
||||
else:
|
||||
formatter.write(f"No options match '[highlight]{subtopic}[/highlight]'")
|
||||
return formatter.getvalue()
|
||||
|
||||
|
||||
def get_matching_options(
|
||||
command: click.Command, ctx: click.Context, topic: str
|
||||
) -> t.List:
|
||||
"""Get matching options for a command that contain a topic
|
||||
|
||||
Args:
|
||||
command: click.Command
|
||||
ctx: click.Context
|
||||
topic: str, topic to match
|
||||
|
||||
Returns:
|
||||
list of matching click.Option objects
|
||||
|
||||
"""
|
||||
options = []
|
||||
topic = topic.lower()
|
||||
for option in command.params:
|
||||
help_record = option.get_help_record(ctx)
|
||||
if help_record and (topic in help_record[0] or topic in help_record[1]):
|
||||
options.append(option)
|
||||
return options
|
||||
|
||||
|
||||
def format_options_help(
|
||||
options: t.List[click.Option], ctx: click.Context, highlight: t.Optional[str] = None
|
||||
) -> str:
|
||||
"""Format options help for display
|
||||
|
||||
Args:
|
||||
options: list of click.Option objects
|
||||
ctx: click.Context
|
||||
highlight: str, if set, add rich highlighting to options that match highlight str
|
||||
|
||||
Returns:
|
||||
str with formatted help
|
||||
|
||||
"""
|
||||
formatter = click.HelpFormatter()
|
||||
opt_help = [opt.get_help_record(ctx) for opt in options]
|
||||
if highlight:
|
||||
# convert list of tuples to list of lists
|
||||
opt_help = [list(opt) for opt in opt_help]
|
||||
for record in opt_help:
|
||||
record[0] = re.sub(
|
||||
f"({highlight})",
|
||||
"[highlight]\\1[/highlight]",
|
||||
record[0],
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
record[1] = re.sub(
|
||||
f"({highlight})",
|
||||
"[highlight]\\1[/highlight]",
|
||||
record[1],
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
# convert back to list of tuples as that's what write_dl expects
|
||||
opt_help = [tuple(opt) for opt in opt_help]
|
||||
formatter.write_dl(opt_help)
|
||||
return formatter.getvalue()
|
||||
|
||||
|
||||
def format_help_text(text: str, formatter: click.HelpFormatter):
|
||||
text = inspect.cleandoc(text).partition("\f")[0]
|
||||
formatter.write_paragraph()
|
||||
|
||||
with formatter.indentation():
|
||||
formatter.write_text(text)
|
||||
|
||||
|
||||
# TODO: The following help text could probably be done as mako template
|
||||
class ExportCommand(click.Command):
|
||||
@@ -282,19 +424,6 @@ def template_help(width=78):
|
||||
return help_str
|
||||
|
||||
|
||||
def tutorial_help(width=78):
|
||||
"""Return formatted string for tutorial"""
|
||||
sio = io.StringIO()
|
||||
console = Console(file=sio, force_terminal=True, width=width)
|
||||
help_md = get_tutorial_text()
|
||||
help_md = strip_html_comments(help_md)
|
||||
help_md = strip_md_links(help_md)
|
||||
console.print(Markdown(help_md))
|
||||
help_str = sio.getvalue()
|
||||
sio.close()
|
||||
return help_str
|
||||
|
||||
|
||||
def rich_text(text, width=78):
|
||||
"""Return rich formatted text"""
|
||||
sio = io.StringIO()
|
||||
@@ -348,12 +477,3 @@ def strip_md_links(md):
|
||||
def strip_html_comments(text):
|
||||
"""Strip html comments from text (which doesn't need to be valid HTML)"""
|
||||
return re.sub(r"<!--(.|\s|\n)*?-->", "", text)
|
||||
|
||||
|
||||
def get_tutorial_text():
|
||||
"""Load tutorial text from file"""
|
||||
# TODO: would be better to use importlib.abc.ResourceReader but I can't find a single example of how to do this
|
||||
help_file = pathlib.Path(__file__).parent / "tutorial.md"
|
||||
with open(help_file, "r") as fd:
|
||||
md = fd.read()
|
||||
return md
|
||||
72
osxphotos/cli/info.py
Normal file
72
osxphotos/cli/info.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""info command for osxphotos CLI"""
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
import yaml
|
||||
|
||||
import osxphotos
|
||||
from osxphotos._constants import _PHOTOS_4_VERSION
|
||||
|
||||
from .common import DB_ARGUMENT, DB_OPTION, JSON_OPTION, get_photos_db
|
||||
from .list import _list_libraries
|
||||
|
||||
|
||||
@click.command()
|
||||
@DB_OPTION
|
||||
@JSON_OPTION
|
||||
@DB_ARGUMENT
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
def info(ctx, cli_obj, db, json_, photos_library):
|
||||
"""Print out descriptive info of the Photos library database."""
|
||||
|
||||
db = get_photos_db(*photos_library, db, cli_obj.db)
|
||||
if db is None:
|
||||
click.echo(ctx.obj.group.commands["info"].get_help(ctx), err=True)
|
||||
click.echo("\n\nLocated the following Photos library databases: ", err=True)
|
||||
_list_libraries()
|
||||
return
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=db)
|
||||
info = {"database_path": photosdb.db_path, "database_version": photosdb.db_version}
|
||||
photos = photosdb.photos(movies=False)
|
||||
not_shared_photos = [p for p in photos if not p.shared]
|
||||
info["photo_count"] = len(not_shared_photos)
|
||||
|
||||
hidden = [p for p in photos if p.hidden]
|
||||
info["hidden_photo_count"] = len(hidden)
|
||||
|
||||
movies = photosdb.photos(images=False, movies=True)
|
||||
not_shared_movies = [p for p in movies if not p.shared]
|
||||
info["movie_count"] = len(not_shared_movies)
|
||||
|
||||
if photosdb.db_version > _PHOTOS_4_VERSION:
|
||||
shared_photos = [p for p in photos if p.shared]
|
||||
info["shared_photo_count"] = len(shared_photos)
|
||||
|
||||
shared_movies = [p for p in movies if p.shared]
|
||||
info["shared_movie_count"] = len(shared_movies)
|
||||
|
||||
keywords = photosdb.keywords_as_dict
|
||||
info["keywords_count"] = len(keywords)
|
||||
info["keywords"] = keywords
|
||||
|
||||
albums = photosdb.albums_as_dict
|
||||
info["albums_count"] = len(albums)
|
||||
info["albums"] = albums
|
||||
|
||||
if photosdb.db_version > _PHOTOS_4_VERSION:
|
||||
albums_shared = photosdb.albums_shared_as_dict
|
||||
info["shared_albums_count"] = len(albums_shared)
|
||||
info["shared_albums"] = albums_shared
|
||||
|
||||
persons = photosdb.persons_as_dict
|
||||
|
||||
info["persons_count"] = len(persons)
|
||||
info["persons"] = persons
|
||||
|
||||
if cli_obj.json or json_:
|
||||
click.echo(json.dumps(info, ensure_ascii=False))
|
||||
else:
|
||||
click.echo(yaml.dump(info, sort_keys=False, allow_unicode=True))
|
||||
37
osxphotos/cli/install_uninstall_run.py
Normal file
37
osxphotos/cli/install_uninstall_run.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""install/uninstall/run commands for osxphotos CLI"""
|
||||
|
||||
import sys
|
||||
from runpy import run_module, run_path
|
||||
|
||||
import click
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("packages", nargs=-1, required=True)
|
||||
@click.option(
|
||||
"-U", "--upgrade", is_flag=True, help="Upgrade packages to latest version"
|
||||
)
|
||||
def install(packages, upgrade):
|
||||
"""Install Python packages into the same environment as osxphotos"""
|
||||
args = ["pip", "install"]
|
||||
if upgrade:
|
||||
args += ["--upgrade"]
|
||||
args += list(packages)
|
||||
sys.argv = args
|
||||
run_module("pip", run_name="__main__")
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("packages", nargs=-1, required=True)
|
||||
@click.option("-y", "--yes", is_flag=True, help="Don't ask for confirmation")
|
||||
def uninstall(packages, yes):
|
||||
"""Uninstall Python packages from the osxphotos environment"""
|
||||
sys.argv = ["pip", "uninstall"] + list(packages) + (["-y"] if yes else [])
|
||||
run_module("pip", run_name="__main__")
|
||||
|
||||
|
||||
@click.command(name="run")
|
||||
@click.argument("python_file", nargs=1, type=click.Path(exists=True))
|
||||
def run(python_file):
|
||||
"""Run a python file using same environment as osxphotos"""
|
||||
run_path(python_file, run_name="__main__")
|
||||
37
osxphotos/cli/keywords.py
Normal file
37
osxphotos/cli/keywords.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""keywords command for osxphotos CLI"""
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
import yaml
|
||||
|
||||
import osxphotos
|
||||
|
||||
from .common import DB_ARGUMENT, DB_OPTION, JSON_OPTION, get_photos_db
|
||||
from .list import _list_libraries
|
||||
|
||||
|
||||
@click.command()
|
||||
@DB_OPTION
|
||||
@JSON_OPTION
|
||||
@DB_ARGUMENT
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
def keywords(ctx, cli_obj, db, json_, photos_library):
|
||||
"""Print out keywords found in the Photos library."""
|
||||
|
||||
# below needed for to make CliRunner work for testing
|
||||
cli_db = cli_obj.db if cli_obj is not None else None
|
||||
db = get_photos_db(*photos_library, db, cli_db)
|
||||
if db is None:
|
||||
click.echo(ctx.obj.group.commands["keywords"].get_help(ctx), err=True)
|
||||
click.echo("\n\nLocated the following Photos library databases: ", err=True)
|
||||
_list_libraries()
|
||||
return
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=db)
|
||||
keywords = {"keywords": photosdb.keywords_as_dict}
|
||||
if json_ or cli_obj.json:
|
||||
click.echo(json.dumps(keywords, ensure_ascii=False))
|
||||
else:
|
||||
click.echo(yaml.dump(keywords, sort_keys=False, allow_unicode=True))
|
||||
37
osxphotos/cli/labels.py
Normal file
37
osxphotos/cli/labels.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""labels command for osxphotos CLI"""
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
import yaml
|
||||
|
||||
import osxphotos
|
||||
|
||||
from .common import DB_ARGUMENT, DB_OPTION, JSON_OPTION, get_photos_db
|
||||
from .list import _list_libraries
|
||||
|
||||
|
||||
@click.command()
|
||||
@DB_OPTION
|
||||
@JSON_OPTION
|
||||
@DB_ARGUMENT
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
def labels(ctx, cli_obj, db, json_, photos_library):
|
||||
"""Print out image classification labels found in the Photos library."""
|
||||
|
||||
# below needed for to make CliRunner work for testing
|
||||
cli_db = cli_obj.db if cli_obj is not None else None
|
||||
db = get_photos_db(*photos_library, db, cli_db)
|
||||
if db is None:
|
||||
click.echo(ctx.obj.group.commands["labels"].get_help(ctx), err=True)
|
||||
click.echo("\n\nLocated the following Photos library databases: ", err=True)
|
||||
_list_libraries()
|
||||
return
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=db)
|
||||
labels = {"labels": photosdb.labels_as_dict}
|
||||
if json_ or cli_obj.json:
|
||||
click.echo(json.dumps(labels, ensure_ascii=False))
|
||||
else:
|
||||
click.echo(yaml.dump(labels, sort_keys=False, allow_unicode=True))
|
||||
57
osxphotos/cli/list.py
Normal file
57
osxphotos/cli/list.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""list command for osxphotos CLI"""
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
|
||||
import osxphotos
|
||||
|
||||
from .common import JSON_OPTION
|
||||
|
||||
|
||||
@click.command(name="list")
|
||||
@JSON_OPTION
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
def list_libraries(ctx, cli_obj, json_):
|
||||
"""Print list of Photos libraries found on the system."""
|
||||
|
||||
# implemented in _list_libraries so it can be called by other CLI functions
|
||||
# without errors due to passing ctx and cli_obj
|
||||
_list_libraries(json_=json_ or cli_obj.json, error=False)
|
||||
|
||||
|
||||
def _list_libraries(json_=False, error=True):
|
||||
"""Print list of Photos libraries found on the system.
|
||||
If json_ == True, print output as JSON (default = False)"""
|
||||
|
||||
photo_libs = osxphotos.utils.list_photo_libraries()
|
||||
sys_lib = osxphotos.utils.get_system_library_path()
|
||||
last_lib = osxphotos.utils.get_last_library_path()
|
||||
|
||||
if json_:
|
||||
libs = {
|
||||
"photo_libraries": photo_libs,
|
||||
"system_library": sys_lib,
|
||||
"last_library": last_lib,
|
||||
}
|
||||
click.echo(json.dumps(libs, ensure_ascii=False))
|
||||
else:
|
||||
last_lib_flag = sys_lib_flag = False
|
||||
|
||||
for lib in photo_libs:
|
||||
if lib == sys_lib:
|
||||
click.echo(f"(*)\t{lib}", err=error)
|
||||
sys_lib_flag = True
|
||||
elif lib == last_lib:
|
||||
click.echo(f"(#)\t{lib}", err=error)
|
||||
last_lib_flag = True
|
||||
else:
|
||||
click.echo(f"\t{lib}", err=error)
|
||||
|
||||
if sys_lib_flag or last_lib_flag:
|
||||
click.echo("\n", err=error)
|
||||
if sys_lib_flag:
|
||||
click.echo("(*)\tSystem Photos Library", err=error)
|
||||
if last_lib_flag:
|
||||
click.echo("(#)\tLast opened Photos Library", err=error)
|
||||
108
osxphotos/cli/param_types.py
Normal file
108
osxphotos/cli/param_types.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Click parameter types for osxphotos CLI"""
|
||||
import datetime
|
||||
import pathlib
|
||||
|
||||
import bitmath
|
||||
import click
|
||||
|
||||
from osxphotos.export_db_utils import export_db_get_version
|
||||
from osxphotos.utils import expand_and_validate_filepath, load_function
|
||||
|
||||
__all__ = [
|
||||
"BitMathSize",
|
||||
"DateTimeISO8601",
|
||||
"ExportDBType",
|
||||
"FunctionCall",
|
||||
"TimeISO8601",
|
||||
]
|
||||
|
||||
|
||||
class DateTimeISO8601(click.ParamType):
|
||||
|
||||
name = "DATETIME"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return datetime.datetime.fromisoformat(value)
|
||||
except Exception:
|
||||
self.fail(
|
||||
f"Invalid datetime format {value}. "
|
||||
"Valid format: YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]]"
|
||||
)
|
||||
|
||||
|
||||
class BitMathSize(click.ParamType):
|
||||
|
||||
name = "BITMATH"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
value = bitmath.parse_string(value)
|
||||
except ValueError:
|
||||
# no units specified
|
||||
try:
|
||||
value = int(value)
|
||||
value = bitmath.Byte(value)
|
||||
except ValueError as e:
|
||||
self.fail(
|
||||
f"{value} must be specified as bytes or using SI/NIST units. "
|
||||
+ "For example, the following are all valid and equivalent sizes: '1048576' '1.048576MB', '1 MiB'."
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
class TimeISO8601(click.ParamType):
|
||||
|
||||
name = "TIME"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return datetime.time.fromisoformat(value).replace(tzinfo=None)
|
||||
except Exception:
|
||||
self.fail(
|
||||
f"Invalid time format {value}. "
|
||||
"Valid format: HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]] "
|
||||
"however, note that timezone will be ignored."
|
||||
)
|
||||
|
||||
|
||||
class FunctionCall(click.ParamType):
|
||||
name = "FUNCTION"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
if "::" not in value:
|
||||
self.fail(
|
||||
f"Could not parse function name from '{value}'. "
|
||||
"Valid format filename.py::function"
|
||||
)
|
||||
|
||||
filename, funcname = value.split("::")
|
||||
|
||||
filename_validated = expand_and_validate_filepath(filename)
|
||||
if not filename_validated:
|
||||
self.fail(f"'{filename}' does not appear to be a file")
|
||||
|
||||
try:
|
||||
function = load_function(filename_validated, funcname)
|
||||
except Exception as e:
|
||||
self.fail(f"Could not load function {funcname} from {filename_validated}")
|
||||
|
||||
return (function, value)
|
||||
|
||||
|
||||
class ExportDBType(click.ParamType):
|
||||
|
||||
name = "EXPORTDB"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
export_db_name = pathlib.Path(value)
|
||||
if export_db_name.is_dir():
|
||||
raise click.BadParameter(f"{value} is a directory")
|
||||
if export_db_name.is_file():
|
||||
# verify it's actually an osxphotos export_db
|
||||
# export_db_get_version will raise an error if it's not valid
|
||||
osxphotos_ver, export_db_ver = export_db_get_version(value)
|
||||
return value
|
||||
except Exception:
|
||||
self.fail(f"{value} exists but is not a valid osxphotos export database. ")
|
||||
36
osxphotos/cli/persons.py
Normal file
36
osxphotos/cli/persons.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""persons command for osxphotos CLI"""
|
||||
import json
|
||||
|
||||
import click
|
||||
import yaml
|
||||
|
||||
import osxphotos
|
||||
|
||||
from .common import DB_ARGUMENT, DB_OPTION, JSON_OPTION, get_photos_db
|
||||
from .list import _list_libraries
|
||||
|
||||
|
||||
@click.command()
|
||||
@DB_OPTION
|
||||
@JSON_OPTION
|
||||
@DB_ARGUMENT
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
def persons(ctx, cli_obj, db, json_, photos_library):
|
||||
"""Print out persons (faces) found in the Photos library."""
|
||||
|
||||
# below needed for to make CliRunner work for testing
|
||||
cli_db = cli_obj.db if cli_obj is not None else None
|
||||
db = get_photos_db(*photos_library, db, cli_db)
|
||||
if db is None:
|
||||
click.echo(ctx.obj.group.commands["persons"].get_help(ctx), err=True)
|
||||
click.echo("\n\nLocated the following Photos library databases: ", err=True)
|
||||
_list_libraries()
|
||||
return
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=db)
|
||||
persons = {"persons": photosdb.persons_as_dict}
|
||||
if json_ or cli_obj.json:
|
||||
click.echo(json.dumps(persons, ensure_ascii=False))
|
||||
else:
|
||||
click.echo(yaml.dump(persons, sort_keys=False, allow_unicode=True))
|
||||
62
osxphotos/cli/places.py
Normal file
62
osxphotos/cli/places.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""places command for osxphotos CLI"""
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
import yaml
|
||||
|
||||
import osxphotos
|
||||
from osxphotos._constants import _PHOTOS_4_VERSION, _UNKNOWN_PLACE
|
||||
|
||||
from .common import DB_ARGUMENT, DB_OPTION, JSON_OPTION, get_photos_db
|
||||
from .list import _list_libraries
|
||||
|
||||
|
||||
@click.command()
|
||||
@DB_OPTION
|
||||
@JSON_OPTION
|
||||
@DB_ARGUMENT
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
def places(ctx, cli_obj, db, json_, photos_library):
|
||||
"""Print out places found in the Photos library."""
|
||||
|
||||
# below needed for to make CliRunner work for testing
|
||||
cli_db = cli_obj.db if cli_obj is not None else None
|
||||
db = get_photos_db(*photos_library, db, cli_db)
|
||||
if db is None:
|
||||
click.echo(ctx.obj.group.commands["places"].get_help(ctx), err=True)
|
||||
click.echo("\n\nLocated the following Photos library databases: ", err=True)
|
||||
_list_libraries()
|
||||
return
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=db)
|
||||
place_names = {}
|
||||
for photo in photosdb.photos(movies=True):
|
||||
if photo.place:
|
||||
try:
|
||||
place_names[photo.place.name] += 1
|
||||
except Exception:
|
||||
place_names[photo.place.name] = 1
|
||||
else:
|
||||
try:
|
||||
place_names[_UNKNOWN_PLACE] += 1
|
||||
except Exception:
|
||||
place_names[_UNKNOWN_PLACE] = 1
|
||||
|
||||
# sort by place count
|
||||
places = {
|
||||
"places": {
|
||||
name: place_names[name]
|
||||
for name in sorted(
|
||||
place_names.keys(), key=lambda key: place_names[key], reverse=True
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
# below needed for to make CliRunner work for testing
|
||||
cli_json = cli_obj.json if cli_obj is not None else None
|
||||
if json_ or cli_json:
|
||||
click.echo(json.dumps(places, ensure_ascii=False))
|
||||
else:
|
||||
click.echo(yaml.dump(places, sort_keys=False, allow_unicode=True))
|
||||
112
osxphotos/cli/print_photo_info.py
Normal file
112
osxphotos/cli/print_photo_info.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""print_photo_info function to print PhotoInfo objects"""
|
||||
|
||||
import csv
|
||||
import sys
|
||||
from typing import Callable, List
|
||||
|
||||
from osxphotos.photoinfo import PhotoInfo
|
||||
|
||||
|
||||
def print_photo_info(
|
||||
photos: List[PhotoInfo], json: bool = False, print_func: Callable = print
|
||||
):
|
||||
dump = []
|
||||
if json:
|
||||
dump.extend(p.json() for p in photos)
|
||||
print_func(f"[{', '.join(dump)}]")
|
||||
else:
|
||||
# dump as CSV
|
||||
csv_writer = csv.writer(
|
||||
sys.stdout, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
|
||||
)
|
||||
# add headers
|
||||
dump.append(
|
||||
[
|
||||
"uuid",
|
||||
"filename",
|
||||
"original_filename",
|
||||
"date",
|
||||
"description",
|
||||
"title",
|
||||
"keywords",
|
||||
"albums",
|
||||
"persons",
|
||||
"path",
|
||||
"ismissing",
|
||||
"hasadjustments",
|
||||
"external_edit",
|
||||
"favorite",
|
||||
"hidden",
|
||||
"shared",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"path_edited",
|
||||
"isphoto",
|
||||
"ismovie",
|
||||
"uti",
|
||||
"burst",
|
||||
"live_photo",
|
||||
"path_live_photo",
|
||||
"iscloudasset",
|
||||
"incloud",
|
||||
"date_modified",
|
||||
"portrait",
|
||||
"screenshot",
|
||||
"slow_mo",
|
||||
"time_lapse",
|
||||
"hdr",
|
||||
"selfie",
|
||||
"panorama",
|
||||
"has_raw",
|
||||
"uti_raw",
|
||||
"path_raw",
|
||||
"intrash",
|
||||
]
|
||||
)
|
||||
for p in photos:
|
||||
date_modified_iso = p.date_modified.isoformat() if p.date_modified else None
|
||||
dump.append(
|
||||
[
|
||||
p.uuid,
|
||||
p.filename,
|
||||
p.original_filename,
|
||||
p.date.isoformat(),
|
||||
p.description,
|
||||
p.title,
|
||||
", ".join(p.keywords),
|
||||
", ".join(p.albums),
|
||||
", ".join(p.persons),
|
||||
p.path,
|
||||
p.ismissing,
|
||||
p.hasadjustments,
|
||||
p.external_edit,
|
||||
p.favorite,
|
||||
p.hidden,
|
||||
p.shared,
|
||||
p._latitude,
|
||||
p._longitude,
|
||||
p.path_edited,
|
||||
p.isphoto,
|
||||
p.ismovie,
|
||||
p.uti,
|
||||
p.burst,
|
||||
p.live_photo,
|
||||
p.path_live_photo,
|
||||
p.iscloudasset,
|
||||
p.incloud,
|
||||
date_modified_iso,
|
||||
p.portrait,
|
||||
p.screenshot,
|
||||
p.slow_mo,
|
||||
p.time_lapse,
|
||||
p.hdr,
|
||||
p.selfie,
|
||||
p.panorama,
|
||||
p.has_raw,
|
||||
p.uti_raw,
|
||||
p.path_raw,
|
||||
p.intrash,
|
||||
]
|
||||
)
|
||||
for row in dump:
|
||||
csv_writer.writerow(row)
|
||||
354
osxphotos/cli/query.py
Normal file
354
osxphotos/cli/query.py
Normal file
@@ -0,0 +1,354 @@
|
||||
"""query command for osxphotos CLI"""
|
||||
|
||||
import click
|
||||
|
||||
import osxphotos
|
||||
from osxphotos.debug import set_debug
|
||||
from osxphotos.photosalbum import PhotosAlbum
|
||||
from osxphotos.queryoptions import QueryOptions
|
||||
|
||||
from .common import (
|
||||
CLI_COLOR_ERROR,
|
||||
CLI_COLOR_WARNING,
|
||||
DB_ARGUMENT,
|
||||
DB_OPTION,
|
||||
DELETED_OPTIONS,
|
||||
JSON_OPTION,
|
||||
OSXPHOTOS_HIDDEN,
|
||||
QUERY_OPTIONS,
|
||||
get_photos_db,
|
||||
load_uuid_from_file,
|
||||
)
|
||||
from .list import _list_libraries
|
||||
from .print_photo_info import print_photo_info
|
||||
|
||||
|
||||
@click.command()
|
||||
@DB_OPTION
|
||||
@JSON_OPTION
|
||||
@QUERY_OPTIONS
|
||||
@DELETED_OPTIONS
|
||||
@click.option("--missing", is_flag=True, help="Search for photos missing from disk.")
|
||||
@click.option(
|
||||
"--not-missing",
|
||||
is_flag=True,
|
||||
help="Search for photos present on disk (e.g. not missing).",
|
||||
)
|
||||
@click.option(
|
||||
"--cloudasset",
|
||||
is_flag=True,
|
||||
help="Search for photos that are part of an iCloud library",
|
||||
)
|
||||
@click.option(
|
||||
"--not-cloudasset",
|
||||
is_flag=True,
|
||||
help="Search for photos that are not part of an iCloud library",
|
||||
)
|
||||
@click.option(
|
||||
"--incloud",
|
||||
is_flag=True,
|
||||
help="Search for photos that are in iCloud (have been synched)",
|
||||
)
|
||||
@click.option(
|
||||
"--not-incloud",
|
||||
is_flag=True,
|
||||
help="Search for photos that are not in iCloud (have not been synched)",
|
||||
)
|
||||
@click.option(
|
||||
"--add-to-album",
|
||||
metavar="ALBUM",
|
||||
help="Add all photos from query to album ALBUM in Photos. Album ALBUM will be created "
|
||||
"if it doesn't exist. All photos in the query results will be added to this album. "
|
||||
"This only works if the Photos library being queried is the last-opened (default) library in Photos. "
|
||||
"This feature is currently experimental. I don't know how well it will work on large query sets.",
|
||||
)
|
||||
@click.option(
|
||||
"--debug", required=False, is_flag=True, default=False, hidden=OSXPHOTOS_HIDDEN
|
||||
)
|
||||
@DB_ARGUMENT
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
def query(
|
||||
ctx,
|
||||
cli_obj,
|
||||
db,
|
||||
photos_library,
|
||||
keyword,
|
||||
person,
|
||||
album,
|
||||
folder,
|
||||
name,
|
||||
uuid,
|
||||
uuid_from_file,
|
||||
title,
|
||||
no_title,
|
||||
description,
|
||||
no_description,
|
||||
ignore_case,
|
||||
json_,
|
||||
edited,
|
||||
external_edit,
|
||||
favorite,
|
||||
not_favorite,
|
||||
hidden,
|
||||
not_hidden,
|
||||
missing,
|
||||
not_missing,
|
||||
shared,
|
||||
not_shared,
|
||||
only_movies,
|
||||
only_photos,
|
||||
uti,
|
||||
burst,
|
||||
not_burst,
|
||||
live,
|
||||
not_live,
|
||||
cloudasset,
|
||||
not_cloudasset,
|
||||
incloud,
|
||||
not_incloud,
|
||||
from_date,
|
||||
to_date,
|
||||
from_time,
|
||||
to_time,
|
||||
portrait,
|
||||
not_portrait,
|
||||
screenshot,
|
||||
not_screenshot,
|
||||
slow_mo,
|
||||
not_slow_mo,
|
||||
time_lapse,
|
||||
not_time_lapse,
|
||||
hdr,
|
||||
not_hdr,
|
||||
selfie,
|
||||
not_selfie,
|
||||
panorama,
|
||||
not_panorama,
|
||||
has_raw,
|
||||
place,
|
||||
no_place,
|
||||
location,
|
||||
no_location,
|
||||
label,
|
||||
deleted,
|
||||
deleted_only,
|
||||
has_comment,
|
||||
no_comment,
|
||||
has_likes,
|
||||
no_likes,
|
||||
is_reference,
|
||||
in_album,
|
||||
not_in_album,
|
||||
duplicate,
|
||||
min_size,
|
||||
max_size,
|
||||
regex,
|
||||
selected,
|
||||
exif,
|
||||
query_eval,
|
||||
query_function,
|
||||
add_to_album,
|
||||
debug, # handled in cli/__init__.py
|
||||
):
|
||||
"""Query the Photos database using 1 or more search options;
|
||||
if more than one option is provided, they are treated as "AND"
|
||||
(e.g. search for photos matching all options).
|
||||
"""
|
||||
|
||||
# if no query terms, show help and return
|
||||
# sanity check input args
|
||||
nonexclusive = [
|
||||
keyword,
|
||||
person,
|
||||
album,
|
||||
folder,
|
||||
name,
|
||||
uuid,
|
||||
uuid_from_file,
|
||||
edited,
|
||||
external_edit,
|
||||
uti,
|
||||
has_raw,
|
||||
from_date,
|
||||
to_date,
|
||||
from_time,
|
||||
to_time,
|
||||
label,
|
||||
is_reference,
|
||||
query_eval,
|
||||
query_function,
|
||||
min_size,
|
||||
max_size,
|
||||
regex,
|
||||
selected,
|
||||
exif,
|
||||
duplicate,
|
||||
]
|
||||
exclusive = [
|
||||
(favorite, not_favorite),
|
||||
(hidden, not_hidden),
|
||||
(missing, not_missing),
|
||||
(any(title), no_title),
|
||||
(any(description), no_description),
|
||||
(only_photos, only_movies),
|
||||
(burst, not_burst),
|
||||
(live, not_live),
|
||||
(cloudasset, not_cloudasset),
|
||||
(incloud, not_incloud),
|
||||
(portrait, not_portrait),
|
||||
(screenshot, not_screenshot),
|
||||
(slow_mo, not_slow_mo),
|
||||
(time_lapse, not_time_lapse),
|
||||
(hdr, not_hdr),
|
||||
(selfie, not_selfie),
|
||||
(panorama, not_panorama),
|
||||
(any(place), no_place),
|
||||
(deleted, deleted_only),
|
||||
(shared, not_shared),
|
||||
(has_comment, no_comment),
|
||||
(has_likes, no_likes),
|
||||
(in_album, not_in_album),
|
||||
(location, no_location),
|
||||
]
|
||||
# print help if no non-exclusive term or a double exclusive term is given
|
||||
if any(all(bb) for bb in exclusive) or not any(
|
||||
nonexclusive + [b ^ n for b, n in exclusive]
|
||||
):
|
||||
click.echo("Incompatible query options", err=True)
|
||||
click.echo(ctx.obj.group.commands["query"].get_help(ctx), err=True)
|
||||
return
|
||||
|
||||
# actually have something to query
|
||||
# default searches for everything
|
||||
photos = True
|
||||
movies = True
|
||||
if only_movies:
|
||||
photos = False
|
||||
if only_photos:
|
||||
movies = False
|
||||
|
||||
# load UUIDs if necessary and append to any uuids passed with --uuid
|
||||
if uuid_from_file:
|
||||
uuid_list = list(uuid) # Click option is a tuple
|
||||
uuid_list.extend(load_uuid_from_file(uuid_from_file))
|
||||
uuid = tuple(uuid_list)
|
||||
|
||||
# below needed for to make CliRunner work for testing
|
||||
cli_db = cli_obj.db if cli_obj is not None else None
|
||||
db = get_photos_db(*photos_library, db, cli_db)
|
||||
if db is None:
|
||||
click.echo(ctx.obj.group.commands["query"].get_help(ctx), err=True)
|
||||
click.echo("\n\nLocated the following Photos library databases: ", err=True)
|
||||
_list_libraries()
|
||||
return
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=db)
|
||||
query_options = QueryOptions(
|
||||
keyword=keyword,
|
||||
person=person,
|
||||
album=album,
|
||||
folder=folder,
|
||||
uuid=uuid,
|
||||
title=title,
|
||||
no_title=no_title,
|
||||
description=description,
|
||||
no_description=no_description,
|
||||
ignore_case=ignore_case,
|
||||
edited=edited,
|
||||
external_edit=external_edit,
|
||||
favorite=favorite,
|
||||
not_favorite=not_favorite,
|
||||
hidden=hidden,
|
||||
not_hidden=not_hidden,
|
||||
missing=missing,
|
||||
not_missing=not_missing,
|
||||
shared=shared,
|
||||
not_shared=not_shared,
|
||||
photos=photos,
|
||||
movies=movies,
|
||||
uti=uti,
|
||||
burst=burst,
|
||||
not_burst=not_burst,
|
||||
live=live,
|
||||
not_live=not_live,
|
||||
cloudasset=cloudasset,
|
||||
not_cloudasset=not_cloudasset,
|
||||
incloud=incloud,
|
||||
not_incloud=not_incloud,
|
||||
from_date=from_date,
|
||||
to_date=to_date,
|
||||
from_time=from_time,
|
||||
to_time=to_time,
|
||||
portrait=portrait,
|
||||
not_portrait=not_portrait,
|
||||
screenshot=screenshot,
|
||||
not_screenshot=not_screenshot,
|
||||
slow_mo=slow_mo,
|
||||
not_slow_mo=not_slow_mo,
|
||||
time_lapse=time_lapse,
|
||||
not_time_lapse=not_time_lapse,
|
||||
hdr=hdr,
|
||||
not_hdr=not_hdr,
|
||||
selfie=selfie,
|
||||
not_selfie=not_selfie,
|
||||
panorama=panorama,
|
||||
not_panorama=not_panorama,
|
||||
has_raw=has_raw,
|
||||
place=place,
|
||||
no_place=no_place,
|
||||
location=location,
|
||||
no_location=no_location,
|
||||
label=label,
|
||||
deleted=deleted,
|
||||
deleted_only=deleted_only,
|
||||
has_comment=has_comment,
|
||||
no_comment=no_comment,
|
||||
has_likes=has_likes,
|
||||
no_likes=no_likes,
|
||||
is_reference=is_reference,
|
||||
in_album=in_album,
|
||||
not_in_album=not_in_album,
|
||||
name=name,
|
||||
min_size=min_size,
|
||||
max_size=max_size,
|
||||
query_eval=query_eval,
|
||||
function=query_function,
|
||||
regex=regex,
|
||||
selected=selected,
|
||||
exif=exif,
|
||||
duplicate=duplicate,
|
||||
)
|
||||
|
||||
try:
|
||||
photos = photosdb.query(query_options)
|
||||
except ValueError as e:
|
||||
if "Invalid query_eval CRITERIA:" in str(e):
|
||||
msg = str(e).split(":")[1]
|
||||
raise click.BadOptionUsage(
|
||||
"query_eval", f"Invalid query-eval CRITERIA: {msg}"
|
||||
)
|
||||
else:
|
||||
raise ValueError(e)
|
||||
|
||||
# below needed for to make CliRunner work for testing
|
||||
cli_json = cli_obj.json if cli_obj is not None else None
|
||||
|
||||
if add_to_album and photos:
|
||||
album_query = PhotosAlbum(add_to_album, verbose=None)
|
||||
photo_len = len(photos)
|
||||
photo_word = "photos" if photo_len > 1 else "photo"
|
||||
click.echo(
|
||||
f"Adding {photo_len} {photo_word} to album '{album_query.name}'. Note: Photos may prompt you to confirm this action.",
|
||||
err=True,
|
||||
)
|
||||
try:
|
||||
album_query.add_list(photos)
|
||||
except Exception as e:
|
||||
click.secho(
|
||||
f"Error adding photos to album {add_to_album}: {e}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
err=True,
|
||||
)
|
||||
|
||||
print_photo_info(photos, cli_json or json_, print_func=click.echo)
|
||||
339
osxphotos/cli/repl.py
Normal file
339
osxphotos/cli/repl.py
Normal file
@@ -0,0 +1,339 @@
|
||||
"""repl command for osxphotos CLI"""
|
||||
|
||||
import dataclasses
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import sys
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
import click
|
||||
import photoscript
|
||||
from rich import pretty, print
|
||||
|
||||
import osxphotos
|
||||
from osxphotos._constants import _PHOTOS_4_VERSION
|
||||
from osxphotos.photoinfo import PhotoInfo
|
||||
from osxphotos.photosdb import PhotosDB
|
||||
from osxphotos.pyrepl import embed_repl
|
||||
from osxphotos.queryoptions import QueryOptions
|
||||
|
||||
from .common import (
|
||||
DB_ARGUMENT,
|
||||
DB_OPTION,
|
||||
DELETED_OPTIONS,
|
||||
QUERY_OPTIONS,
|
||||
get_photos_db,
|
||||
load_uuid_from_file,
|
||||
)
|
||||
|
||||
|
||||
class IncompatibleQueryOptions(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@click.command(name="repl")
|
||||
@DB_OPTION
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
@click.option(
|
||||
"--emacs",
|
||||
required=False,
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Launch REPL with Emacs keybindings (default is vi bindings)",
|
||||
)
|
||||
@click.option(
|
||||
"--beta",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
hidden=True,
|
||||
help="Enable beta options.",
|
||||
)
|
||||
@QUERY_OPTIONS
|
||||
@DELETED_OPTIONS
|
||||
@click.option("--missing", is_flag=True, help="Search for photos missing from disk.")
|
||||
@click.option(
|
||||
"--not-missing",
|
||||
is_flag=True,
|
||||
help="Search for photos present on disk (e.g. not missing).",
|
||||
)
|
||||
@click.option(
|
||||
"--cloudasset",
|
||||
is_flag=True,
|
||||
help="Search for photos that are part of an iCloud library",
|
||||
)
|
||||
@click.option(
|
||||
"--not-cloudasset",
|
||||
is_flag=True,
|
||||
help="Search for photos that are not part of an iCloud library",
|
||||
)
|
||||
@click.option(
|
||||
"--incloud",
|
||||
is_flag=True,
|
||||
help="Search for photos that are in iCloud (have been synched)",
|
||||
)
|
||||
@click.option(
|
||||
"--not-incloud",
|
||||
is_flag=True,
|
||||
help="Search for photos that are not in iCloud (have not been synched)",
|
||||
)
|
||||
def repl(ctx, cli_obj, db, emacs, beta, **kwargs):
|
||||
"""Run interactive osxphotos REPL shell (useful for debugging, prototyping, and inspecting your Photos library)"""
|
||||
import logging
|
||||
|
||||
from objexplore import explore
|
||||
from photoscript import Album, Photo, PhotosLibrary
|
||||
from rich import inspect as _inspect
|
||||
|
||||
from osxphotos import ExifTool, PhotoInfo, PhotosDB
|
||||
from osxphotos.albuminfo import AlbumInfo
|
||||
from osxphotos.momentinfo import MomentInfo
|
||||
from osxphotos.photoexporter import ExportOptions, ExportResults, PhotoExporter
|
||||
from osxphotos.placeinfo import PlaceInfo
|
||||
from osxphotos.queryoptions import QueryOptions
|
||||
from osxphotos.scoreinfo import ScoreInfo
|
||||
from osxphotos.searchinfo import SearchInfo
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.disabled = True
|
||||
|
||||
pretty.install()
|
||||
print(f"python version: {sys.version}")
|
||||
print(f"osxphotos version: {osxphotos._version.__version__}")
|
||||
db = db or get_photos_db()
|
||||
photosdb = _load_photos_db(db)
|
||||
# enable beta features if requested
|
||||
if beta:
|
||||
photosdb._beta = beta
|
||||
print("Beta mode enabled")
|
||||
print("Getting photos")
|
||||
tic = time.perf_counter()
|
||||
try:
|
||||
query_options = _query_options_from_kwargs(**kwargs)
|
||||
except IncompatibleQueryOptions:
|
||||
click.echo("Incompatible query options", err=True)
|
||||
click.echo(ctx.obj.group.commands["repl"].get_help(ctx), err=True)
|
||||
sys.exit(1)
|
||||
photos = _query_photos(photosdb, query_options)
|
||||
all_photos = _get_all_photos(photosdb)
|
||||
toc = time.perf_counter()
|
||||
tictoc = toc - tic
|
||||
|
||||
# shortcut for helper functions
|
||||
get_photo = photosdb.get_photo
|
||||
show = _show_photo
|
||||
spotlight = _spotlight_photo
|
||||
get_selected = _get_selected(photosdb)
|
||||
try:
|
||||
selected = get_selected()
|
||||
except Exception:
|
||||
# get_selected sometimes fails
|
||||
selected = []
|
||||
|
||||
def inspect(obj):
|
||||
"""inspect object"""
|
||||
return _inspect(obj, methods=True)
|
||||
|
||||
print(f"Found {len(photos)} photos in {tictoc:0.2f} seconds\n")
|
||||
print("The following classes have been imported from osxphotos:")
|
||||
print(
|
||||
"- AlbumInfo, ExifTool, PhotoInfo, PhotoExporter, ExportOptions, ExportResults, PhotosDB, PlaceInfo, QueryOptions, MomentInfo, ScoreInfo, SearchInfo\n"
|
||||
)
|
||||
print("The following variables are defined:")
|
||||
print(f"- photosdb: PhotosDB() instance for {photosdb.library_path}")
|
||||
print(
|
||||
f"- photos: list of PhotoInfo objects for all photos filtered with any query options passed on command line (len={len(photos)})"
|
||||
)
|
||||
print(
|
||||
f"- all_photos: list of PhotoInfo objects for all photos in photosdb, including those in the trash (len={len(all_photos)})"
|
||||
)
|
||||
print(
|
||||
f"- selected: list of PhotoInfo objects for any photos selected in Photos (len={len(selected)})"
|
||||
)
|
||||
print(f"\nThe following functions may be helpful:")
|
||||
print(
|
||||
f"- get_photo(uuid): return a PhotoInfo object for photo with uuid; e.g. get_photo('B13F4485-94E0-41CD-AF71-913095D62E31')"
|
||||
)
|
||||
print(
|
||||
f"- get_selected(); return list of PhotoInfo objects for photos selected in Photos"
|
||||
)
|
||||
print(
|
||||
f"- show(photo): open a photo object in the default viewer; e.g. show(selected[0])"
|
||||
)
|
||||
print(
|
||||
f"- show(path): open a file at path in the default viewer; e.g. show('/path/to/photo.jpg')"
|
||||
)
|
||||
print(f"- spotlight(photo): open a photo and spotlight it in Photos")
|
||||
# print(
|
||||
# f"- help(object): print help text including list of methods for object; for example, help(PhotosDB)"
|
||||
# )
|
||||
print(
|
||||
f"- inspect(object): print information about an object; e.g. inspect(PhotoInfo)"
|
||||
)
|
||||
print(
|
||||
f"- explore(object): interactively explore an object with objexplore; e.g. explore(PhotoInfo)"
|
||||
)
|
||||
print(f"- q, quit, quit(), exit, exit(): exit this interactive shell\n")
|
||||
|
||||
embed_repl(
|
||||
globals=globals(),
|
||||
locals=locals(),
|
||||
history_filename=str(pathlib.Path.home() / ".osxphotos_repl_history"),
|
||||
quit_words=["q", "quit", "exit"],
|
||||
vi_mode=not emacs,
|
||||
)
|
||||
|
||||
|
||||
def _show_photo(photo: PhotoInfo):
|
||||
"""open image with default image viewer
|
||||
|
||||
Note: This is for debugging only -- it will actually open any filetype which could
|
||||
be very, very bad.
|
||||
|
||||
Args:
|
||||
photo: PhotoInfo object or a path to a photo on disk
|
||||
"""
|
||||
photopath = photo.path if isinstance(photo, osxphotos.PhotoInfo) else photo
|
||||
|
||||
if not os.path.isfile(photopath):
|
||||
return f"'{photopath}' does not appear to be a valid photo path"
|
||||
|
||||
os.system(f"open '{photopath}'")
|
||||
|
||||
|
||||
def _load_photos_db(dbpath):
|
||||
print("Loading database")
|
||||
tic = time.perf_counter()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=dbpath, verbose=print)
|
||||
toc = time.perf_counter()
|
||||
tictoc = toc - tic
|
||||
print(f"Done: took {tictoc:0.2f} seconds")
|
||||
return photosdb
|
||||
|
||||
|
||||
def _get_all_photos(photosdb):
|
||||
"""get list of all photos in photosdb"""
|
||||
photos = photosdb.photos(images=True, movies=True)
|
||||
photos.extend(photosdb.photos(images=True, movies=True, intrash=True))
|
||||
return photos
|
||||
|
||||
|
||||
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])
|
||||
|
||||
return get_selected
|
||||
|
||||
|
||||
def _spotlight_photo(photo: PhotoInfo):
|
||||
photo_ = photoscript.Photo(photo.uuid)
|
||||
photo_.spotlight()
|
||||
|
||||
|
||||
def _query_options_from_kwargs(**kwargs) -> QueryOptions:
|
||||
"""Validate query options and create a QueryOptions instance"""
|
||||
# sanity check input args
|
||||
nonexclusive = [
|
||||
"keyword",
|
||||
"person",
|
||||
"album",
|
||||
"folder",
|
||||
"name",
|
||||
"uuid",
|
||||
"uuid_from_file",
|
||||
"edited",
|
||||
"external_edit",
|
||||
"uti",
|
||||
"has_raw",
|
||||
"from_date",
|
||||
"to_date",
|
||||
"from_time",
|
||||
"to_time",
|
||||
"label",
|
||||
"is_reference",
|
||||
"query_eval",
|
||||
"query_function",
|
||||
"min_size",
|
||||
"max_size",
|
||||
"regex",
|
||||
"selected",
|
||||
"exif",
|
||||
"duplicate",
|
||||
]
|
||||
exclusive = [
|
||||
("favorite", "not_favorite"),
|
||||
("hidden", "not_hidden"),
|
||||
("missing", "not_missing"),
|
||||
("only_photos", "only_movies"),
|
||||
("burst", "not_burst"),
|
||||
("live", "not_live"),
|
||||
("cloudasset", "not_cloudasset"),
|
||||
("incloud", "not_incloud"),
|
||||
("portrait", "not_portrait"),
|
||||
("screenshot", "not_screenshot"),
|
||||
("slow_mo", "not_slow_mo"),
|
||||
("time_lapse", "not_time_lapse"),
|
||||
("hdr", "not_hdr"),
|
||||
("selfie", "not_selfie"),
|
||||
("panorama", "not_panorama"),
|
||||
("deleted", "deleted_only"),
|
||||
("shared", "not_shared"),
|
||||
("has_comment", "no_comment"),
|
||||
("has_likes", "no_likes"),
|
||||
("in_album", "not_in_album"),
|
||||
("location", "no_location"),
|
||||
]
|
||||
# print help if no non-exclusive term or a double exclusive term is given
|
||||
# TODO: add option to validate requiring at least one query arg
|
||||
if any(all([kwargs[b], kwargs[n]]) for b, n in exclusive) or any(
|
||||
[
|
||||
all([any(kwargs["title"]), kwargs["no_title"]]),
|
||||
all([any(kwargs["description"]), kwargs["no_description"]]),
|
||||
all([any(kwargs["place"]), kwargs["no_place"]]),
|
||||
]
|
||||
):
|
||||
raise IncompatibleQueryOptions
|
||||
|
||||
# actually have something to query
|
||||
include_photos = True
|
||||
include_movies = True # default searches for everything
|
||||
if kwargs["only_movies"]:
|
||||
include_photos = False
|
||||
if kwargs["only_photos"]:
|
||||
include_movies = False
|
||||
|
||||
# load UUIDs if necessary and append to any uuids passed with --uuid
|
||||
uuid = None
|
||||
if kwargs["uuid_from_file"]:
|
||||
uuid_list = list(kwargs["uuid"]) # Click option is a tuple
|
||||
uuid_list.extend(load_uuid_from_file(kwargs["uuid_from_file"]))
|
||||
uuid = tuple(uuid_list)
|
||||
|
||||
query_fields = [field.name for field in dataclasses.fields(QueryOptions)]
|
||||
query_dict = {field: kwargs.get(field) for field in query_fields}
|
||||
query_dict["photos"] = include_photos
|
||||
query_dict["movies"] = include_movies
|
||||
query_dict["uuid"] = uuid
|
||||
return QueryOptions(**query_dict)
|
||||
|
||||
|
||||
def _query_photos(photosdb: PhotosDB, query_options: QueryOptions) -> List:
|
||||
"""Query photos given a QueryOptions instance"""
|
||||
try:
|
||||
photos = photosdb.query(query_options)
|
||||
except ValueError as e:
|
||||
if "Invalid query_eval CRITERIA:" not in str(e):
|
||||
raise ValueError(e) from e
|
||||
msg = str(e).split(":")[1]
|
||||
raise click.BadOptionUsage(
|
||||
"query_eval", f"Invalid query-eval CRITERIA: {msg}"
|
||||
) from e
|
||||
|
||||
return photos
|
||||
68
osxphotos/cli/rich_progress.py
Normal file
68
osxphotos/cli/rich_progress.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""rich Progress bar factory that can return a rich Progress bar or a mock Progress bar"""
|
||||
|
||||
import os
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from rich.console import Console
|
||||
from rich.progress import GetTimeCallable, Progress, ProgressColumn, TaskID
|
||||
|
||||
# set to 1 if running tests
|
||||
OSXPHOTOS_IS_TESTING = bool(os.getenv("OSXPHOTOS_IS_TESTING", default=False))
|
||||
|
||||
|
||||
class MockProgress:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def add_task(
|
||||
self,
|
||||
description: str,
|
||||
start: bool = True,
|
||||
total: float = 100.0,
|
||||
completed: int = 0,
|
||||
visible: bool = True,
|
||||
**fields: Any,
|
||||
) -> TaskID:
|
||||
pass
|
||||
|
||||
def advance(self, task_id: TaskID, advance: float = 1) -> None:
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
pass
|
||||
|
||||
|
||||
def rich_progress(
|
||||
*columns: Union[str, ProgressColumn],
|
||||
console: Optional[Console] = None,
|
||||
auto_refresh: bool = True,
|
||||
refresh_per_second: float = 10,
|
||||
speed_estimate_period: float = 30.0,
|
||||
transient: bool = False,
|
||||
redirect_stdout: bool = True,
|
||||
redirect_stderr: bool = True,
|
||||
get_time: Optional[GetTimeCallable] = None,
|
||||
disable: bool = False,
|
||||
expand: bool = False,
|
||||
mock: bool = False,
|
||||
) -> None:
|
||||
"""Return a rich.progress.Progress object unless mock=True or os.getenv("OSXPHOTOS_IS_TESTING") is set"""
|
||||
# if OSXPHOTOS_IS_TESTING is set or mock=True, return a MockProgress object
|
||||
if mock or OSXPHOTOS_IS_TESTING:
|
||||
return MockProgress()
|
||||
return Progress(
|
||||
*columns,
|
||||
console=console,
|
||||
auto_refresh=auto_refresh,
|
||||
refresh_per_second=refresh_per_second,
|
||||
speed_estimate_period=speed_estimate_period,
|
||||
transient=transient,
|
||||
redirect_stdout=redirect_stdout,
|
||||
redirect_stderr=redirect_stderr,
|
||||
get_time=get_time,
|
||||
disable=disable,
|
||||
expand=expand,
|
||||
)
|
||||
158
osxphotos/cli/snap_diff.py
Normal file
158
osxphotos/cli/snap_diff.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""snap/diff commands for osxphotos CLI"""
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
from rich.syntax import Syntax
|
||||
|
||||
import osxphotos
|
||||
|
||||
from .common import DB_OPTION, OSXPHOTOS_SNAPSHOT_DIR, get_photos_db
|
||||
from .verbose import verbose_print
|
||||
|
||||
|
||||
@click.command(name="snap")
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
@DB_OPTION
|
||||
def snap(ctx, cli_obj, db):
|
||||
"""Create snapshot of Photos database to use with diff command
|
||||
|
||||
Snapshots only the database files, not the entire library. If OSXPHOTOS_SNAPSHOT
|
||||
environment variable is defined, will use that as snapshot directory, otherwise
|
||||
uses '/private/tmp/osxphotos_snapshots'
|
||||
|
||||
Works only on Photos library versions since Catalina (10.15) or newer.
|
||||
"""
|
||||
|
||||
db = get_photos_db(db, cli_obj.db)
|
||||
db_path = pathlib.Path(db)
|
||||
if db_path.is_file():
|
||||
# assume it's the sqlite file
|
||||
db_path = db_path.parent.parent
|
||||
db_path = db_path / "database"
|
||||
|
||||
db_folder = os.environ.get("OSXPHOTOS_SNAPSHOT", OSXPHOTOS_SNAPSHOT_DIR)
|
||||
if not os.path.isdir(db_folder):
|
||||
click.echo(f"Creating snapshot folder: '{db_folder}'")
|
||||
os.mkdir(db_folder)
|
||||
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
destination_path = pathlib.Path(db_folder) / timestamp
|
||||
|
||||
# get all the sqlite files including the write ahead log if any
|
||||
files = db_path.glob("*.sqlite*")
|
||||
os.makedirs(destination_path)
|
||||
fu = osxphotos.fileutil.FileUtil()
|
||||
count = 0
|
||||
for file in files:
|
||||
if file.is_file():
|
||||
fu.copy(file, destination_path)
|
||||
count += 1
|
||||
|
||||
print(f"Copied {count} files from {db_path} to {destination_path}")
|
||||
|
||||
|
||||
@click.command(name="diff")
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
@DB_OPTION
|
||||
@click.option(
|
||||
"--raw-output",
|
||||
"-r",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Print raw output (don't use syntax highlighting).",
|
||||
)
|
||||
@click.option(
|
||||
"--style",
|
||||
"-s",
|
||||
metavar="STYLE",
|
||||
nargs=1,
|
||||
default="monokai",
|
||||
help="Specify style/theme for syntax highlighting. "
|
||||
"Theme may be any valid pygments style (https://pygments.org/styles/). "
|
||||
"Default is 'monokai'.",
|
||||
)
|
||||
@click.argument("db2", nargs=-1, type=click.Path(exists=True))
|
||||
@click.option("--verbose", "-V", "verbose", is_flag=True, help="Print verbose output.")
|
||||
def diff(ctx, cli_obj, db, raw_output, style, db2, verbose):
|
||||
"""Compare two Photos databases and print out differences
|
||||
|
||||
To use the diff command, you'll need to install sqldiff via homebrew:
|
||||
|
||||
- Install homebrew (https://brew.sh/) if not already installed
|
||||
|
||||
- Install sqldiff: `brew install sqldiff`
|
||||
|
||||
When run with no arguments, compares the current Photos library to the
|
||||
most recent snapshot in the the OSXPHOTOS_SNAPSHOT directory.
|
||||
|
||||
If run with the --db option, compares the library specified by --db to the
|
||||
most recent snapshot in the the OSXPHOTOS_SNAPSHOT directory.
|
||||
|
||||
If run with just the DB2 argument, compares the current Photos library to
|
||||
the database specified by the DB2 argument.
|
||||
|
||||
If run with both the --db option and the DB2 argument, compares the
|
||||
library specified by --db to the database specified by DB2
|
||||
|
||||
See also `osxphotos snap`
|
||||
|
||||
If the OSXPHOTOS_SNAPSHOT environment variable is not set, will use
|
||||
'/private/tmp/osxphotos_snapshots'
|
||||
|
||||
Works only on Photos library versions since Catalina (10.15) or newer.
|
||||
"""
|
||||
|
||||
verbose_ = verbose_print(verbose, rich=True)
|
||||
|
||||
sqldiff = shutil.which("sqldiff")
|
||||
if not sqldiff:
|
||||
click.echo(
|
||||
"sqldiff not found; install via homebrew (https://brew.sh/): `brew install sqldiff`"
|
||||
)
|
||||
ctx.exit(2)
|
||||
verbose_(f"sqldiff found at '{sqldiff}'")
|
||||
|
||||
db = get_photos_db(db, cli_obj.db)
|
||||
db_path = pathlib.Path(db)
|
||||
if db_path.is_file():
|
||||
# assume it's the sqlite file
|
||||
db_path = db_path.parent.parent
|
||||
db_path = db_path / "database"
|
||||
db_1 = db_path / "photos.sqlite"
|
||||
|
||||
if db2:
|
||||
db_2 = pathlib.Path(db2[0])
|
||||
else:
|
||||
# get most recent snapshot
|
||||
db_folder = os.environ.get("OSXPHOTOS_SNAPSHOT", OSXPHOTOS_SNAPSHOT_DIR)
|
||||
verbose_(f"Using snapshot folder: '{db_folder}'")
|
||||
folders = sorted([f for f in pathlib.Path(db_folder).glob("*") if f.is_dir()])
|
||||
folder_2 = folders[-1]
|
||||
db_2 = folder_2 / "Photos.sqlite"
|
||||
|
||||
if not db_1.exists():
|
||||
print(f"database file {db_1} missing")
|
||||
if not db_2.exists():
|
||||
print(f"database file {db_2} missing")
|
||||
|
||||
verbose_(f"Comparing databases {db_1} and {db_2}")
|
||||
|
||||
diff_proc = subprocess.Popen([sqldiff, db_2, db_1], stdout=subprocess.PIPE)
|
||||
console = Console()
|
||||
for line in iter(diff_proc.stdout.readline, b""):
|
||||
line = line.decode("UTF-8").rstrip()
|
||||
if raw_output:
|
||||
print(line)
|
||||
else:
|
||||
syntax = Syntax(
|
||||
line, "sql", theme=style, line_numbers=False, code_width=1000
|
||||
)
|
||||
console.print(syntax)
|
||||
46
osxphotos/cli/tutorial.py
Normal file
46
osxphotos/cli/tutorial.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""tutorial command for osxphotos CLI"""
|
||||
|
||||
import io
|
||||
import pathlib
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
from rich.markdown import Markdown
|
||||
|
||||
from .help import strip_html_comments, strip_md_links
|
||||
|
||||
|
||||
@click.command(name="tutorial")
|
||||
@click.argument(
|
||||
"WIDTH",
|
||||
nargs=-1,
|
||||
type=click.INT,
|
||||
)
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
def tutorial(ctx, cli_obj, width):
|
||||
"""Display osxphotos tutorial."""
|
||||
width = width[0] if width else 100
|
||||
click.echo_via_pager(tutorial_help(width=width))
|
||||
|
||||
|
||||
def tutorial_help(width=78):
|
||||
"""Return formatted string for tutorial"""
|
||||
sio = io.StringIO()
|
||||
console = Console(file=sio, force_terminal=True, width=width)
|
||||
help_md = get_tutorial_text()
|
||||
help_md = strip_html_comments(help_md)
|
||||
help_md = strip_md_links(help_md)
|
||||
console.print(Markdown(help_md))
|
||||
help_str = sio.getvalue()
|
||||
sio.close()
|
||||
return help_str
|
||||
|
||||
|
||||
def get_tutorial_text():
|
||||
"""Load tutorial text from file"""
|
||||
# TODO: would be better to use importlib.abc.ResourceReader but I can't find a single example of how to do this
|
||||
help_file = pathlib.Path(__file__).parent / "../tutorial.md"
|
||||
with open(help_file, "r") as fd:
|
||||
md = fd.read()
|
||||
return md
|
||||
26
osxphotos/cli/uuid.py
Normal file
26
osxphotos/cli/uuid.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""uuid command for osxphotos CLI"""
|
||||
|
||||
import click
|
||||
import photoscript
|
||||
|
||||
|
||||
@click.command(name="uuid")
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
@click.option(
|
||||
"--filename",
|
||||
"-f",
|
||||
required=False,
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Include filename of selected photos in output",
|
||||
)
|
||||
def uuid(ctx, cli_obj, filename):
|
||||
"""Print out unique IDs (UUID) of photos selected in Photos
|
||||
|
||||
Prints outs UUIDs in form suitable for --uuid-from-file and --skip-uuid-from-file
|
||||
"""
|
||||
for photo in photoscript.PhotosLibrary().selection:
|
||||
if filename:
|
||||
print(f"# {photo.filename}")
|
||||
print(photo.uuid)
|
||||
143
osxphotos/cli/verbose.py
Normal file
143
osxphotos/cli/verbose.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""helper functions for printing verbose output"""
|
||||
|
||||
import os
|
||||
import typing as t
|
||||
from datetime import datetime
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
from rich.theme import Theme
|
||||
|
||||
from .click_rich_echo import rich_click_echo
|
||||
from .common import CLI_COLOR_ERROR, CLI_COLOR_WARNING, time_stamp
|
||||
|
||||
# set to 1 if running tests
|
||||
OSXPHOTOS_IS_TESTING = bool(os.getenv("OSXPHOTOS_IS_TESTING", default=False))
|
||||
|
||||
# include error/warning emoji's in verbose output
|
||||
ERROR_EMOJI = True
|
||||
|
||||
__all__ = ["get_verbose_console", "verbose_print"]
|
||||
|
||||
|
||||
class _Console:
|
||||
"""Store console object for verbose output"""
|
||||
|
||||
def __init__(self):
|
||||
self._console: t.Optional[Console] = None
|
||||
|
||||
@property
|
||||
def console(self):
|
||||
return self._console
|
||||
|
||||
@console.setter
|
||||
def console(self, console: Console):
|
||||
self._console = console
|
||||
|
||||
|
||||
_console = _Console()
|
||||
|
||||
|
||||
def noop(*args, **kwargs):
|
||||
"""no-op function"""
|
||||
pass
|
||||
|
||||
|
||||
def get_verbose_console() -> Console:
|
||||
"""Get console object
|
||||
|
||||
Returns:
|
||||
Console object
|
||||
"""
|
||||
global _console
|
||||
if _console.console is None:
|
||||
_console.console = Console(force_terminal=True)
|
||||
return _console.console
|
||||
|
||||
|
||||
def verbose_print(
|
||||
verbose: bool = True,
|
||||
timestamp: bool = False,
|
||||
rich: bool = False,
|
||||
highlight: bool = False,
|
||||
theme: t.Optional[Theme] = None,
|
||||
**kwargs: t.Any,
|
||||
) -> t.Callable:
|
||||
"""Create verbose function to print output
|
||||
|
||||
Args:
|
||||
verbose: if True, returns verbose print function otherwise returns no-op function
|
||||
timestamp: if True, includes timestamp in verbose output
|
||||
rich: use rich.print instead of click.echo
|
||||
highlight: if True, use automatic rich.print highlighting
|
||||
theme: optional rich.theme.Theme object to use for formatting
|
||||
kwargs: any extra arguments to pass to click.echo or rich.print depending on whether rich==True
|
||||
|
||||
Returns:
|
||||
function to print output
|
||||
"""
|
||||
if not verbose:
|
||||
return noop
|
||||
|
||||
global _console
|
||||
_console.console = Console(theme=theme, width=10_000)
|
||||
|
||||
# closure to capture timestamp
|
||||
def verbose_(*args):
|
||||
"""print output if verbose flag set"""
|
||||
styled_args = []
|
||||
timestamp_str = f"{str(datetime.now())} -- " if timestamp else ""
|
||||
for arg in args:
|
||||
if type(arg) == str:
|
||||
arg = timestamp_str + arg
|
||||
if "error" in arg.lower():
|
||||
arg = click.style(arg, fg=CLI_COLOR_ERROR)
|
||||
elif "warning" in arg.lower():
|
||||
arg = click.style(arg, fg=CLI_COLOR_WARNING)
|
||||
styled_args.append(arg)
|
||||
click.echo(*styled_args, **kwargs)
|
||||
|
||||
def rich_verbose_(*args):
|
||||
"""rich.print output if verbose flag set"""
|
||||
global ERROR_EMOJI
|
||||
timestamp_str = time_stamp() if timestamp else ""
|
||||
new_args = []
|
||||
for arg in args:
|
||||
if type(arg) == str:
|
||||
if "error" in arg.lower():
|
||||
arg = f"[error]{arg}"
|
||||
if ERROR_EMOJI:
|
||||
arg = f":cross_mark-emoji: {arg}"
|
||||
elif "warning" in arg.lower():
|
||||
arg = f"[warning]{arg}"
|
||||
if ERROR_EMOJI:
|
||||
arg = f":warning-emoji: {arg}"
|
||||
arg = timestamp_str + arg
|
||||
new_args.append(arg)
|
||||
_console.console.print(*new_args, highlight=highlight, **kwargs)
|
||||
|
||||
def rich_verbose_testing_(*args):
|
||||
"""print output if verbose flag set using rich.print"""
|
||||
global ERROR_EMOJI
|
||||
timestamp_str = time_stamp() if timestamp else ""
|
||||
new_args = []
|
||||
for arg in args:
|
||||
if type(arg) == str:
|
||||
if "error" in arg.lower():
|
||||
arg = f"[error]{arg}"
|
||||
if ERROR_EMOJI:
|
||||
arg = f":cross_mark-emoji: {arg}"
|
||||
elif "warning" in arg.lower():
|
||||
arg = f"[warning]{arg}"
|
||||
if ERROR_EMOJI:
|
||||
arg = f":warning-emoji: {arg}"
|
||||
arg = timestamp_str + arg
|
||||
new_args.append(arg)
|
||||
rich_click_echo(*new_args, theme=theme, **kwargs)
|
||||
|
||||
if rich and not OSXPHOTOS_IS_TESTING:
|
||||
return rich_verbose_
|
||||
elif rich:
|
||||
return rich_verbose_testing_
|
||||
else:
|
||||
return verbose_
|
||||
@@ -1,4 +1,5 @@
|
||||
""" ConfigOptions class to load/save config settings for osxphotos CLI """
|
||||
import bitmath
|
||||
import toml
|
||||
|
||||
__all__ = [
|
||||
@@ -134,18 +135,12 @@ class ConfigOptions:
|
||||
filename: full path to TOML file to write; filename will be overwritten if it exists
|
||||
"""
|
||||
# todo: add overwrite and option to merge contents already in TOML file (under different [section] with new content)
|
||||
data = {}
|
||||
for attr in sorted(self._attrs.keys()):
|
||||
val = getattr(self, attr)
|
||||
if val in [False, ()]:
|
||||
val = None
|
||||
else:
|
||||
val = list(val) if type(val) == tuple else val
|
||||
|
||||
data[attr] = val
|
||||
|
||||
with open(filename, "w") as fd:
|
||||
toml.dump({self._name: data}, fd)
|
||||
toml.dump(self._get_toml_dict(), fd)
|
||||
|
||||
def write_to_str(self) -> str:
|
||||
"""Write self to TOML str"""
|
||||
return toml.dumps(self._get_toml_dict())
|
||||
|
||||
def load_from_file(self, filename, override=False):
|
||||
"""Load options from a TOML file.
|
||||
@@ -178,3 +173,21 @@ class ConfigOptions:
|
||||
|
||||
def asdict(self):
|
||||
return {attr: getattr(self, attr) for attr in sorted(self._attrs.keys())}
|
||||
|
||||
def _get_toml_dict(self):
|
||||
"""Return dict for writing to TOML file"""
|
||||
data = {}
|
||||
for attr in sorted(self._attrs.keys()):
|
||||
val = getattr(self, attr)
|
||||
|
||||
if isinstance(val, bitmath.Bitmath):
|
||||
val = int(val.to_Byte())
|
||||
|
||||
if val in [False, ()]:
|
||||
val = None
|
||||
else:
|
||||
val = list(val) if type(val) == tuple else val
|
||||
|
||||
data[attr] = val
|
||||
|
||||
return {self._name: data}
|
||||
|
||||
60
osxphotos/crash_reporter.py
Normal file
60
osxphotos/crash_reporter.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Error logger/crash reporter decorator"""
|
||||
|
||||
import datetime
|
||||
import functools
|
||||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from rich import print
|
||||
|
||||
from ._version import __version__
|
||||
|
||||
# store data to print out in crash log, set by set_crash_data
|
||||
CRASH_DATA = {}
|
||||
|
||||
|
||||
def set_crash_data(key_, data):
|
||||
"""Set data to be printed in crash log"""
|
||||
CRASH_DATA[key_] = data
|
||||
|
||||
|
||||
def crash_reporter(filename, message, title, postamble, *extra_args):
|
||||
"""Create a crash dump file on error named filename
|
||||
|
||||
On error, create a crash dump file named filename with exception and stack trace.
|
||||
message is printed to stderr
|
||||
title is printed at beginning of crash dump file
|
||||
postamble is printed to stderr after crash dump file is created
|
||||
If extra_args is not None, any additional arguments to the function will be printed to the file.
|
||||
"""
|
||||
|
||||
def decorated(func):
|
||||
@functools.wraps(func)
|
||||
def wrapped(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
print(message, file=sys.stderr)
|
||||
print(f"[red]{e}[/red]", file=sys.stderr)
|
||||
with open(filename, "w") as f:
|
||||
f.write(f"{title}\n")
|
||||
f.write(f"Created: {datetime.datetime.now()}\n")
|
||||
f.write(f"osxphotos version: {__version__}\n")
|
||||
f.write(f"Platform: {platform.platform()}\n")
|
||||
f.write(f"Python version: {sys.version}\n")
|
||||
f.write(f"sys.argv: {sys.argv}\n")
|
||||
f.write("CRASH_DATA: \\n")
|
||||
for k, v in CRASH_DATA.items():
|
||||
f.write(f"{k}: {v}\n")
|
||||
for arg in extra_args:
|
||||
f.write(f"{arg}\n")
|
||||
f.write(f"Error: {e}\n")
|
||||
traceback.print_exc(file=f)
|
||||
print(f"Crash log written to '{filename}'", file=sys.stderr)
|
||||
print(f"{postamble}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
return wrapped
|
||||
|
||||
return decorated
|
||||
103
osxphotos/debug.py
Normal file
103
osxphotos/debug.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""Utilities for debugging"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Dict, List
|
||||
|
||||
import wrapt
|
||||
from rich import print
|
||||
|
||||
# global variable to control debug output
|
||||
# set via --debug
|
||||
DEBUG = False
|
||||
|
||||
|
||||
def set_debug(debug: bool):
|
||||
"""set debug flag"""
|
||||
global DEBUG
|
||||
DEBUG = debug
|
||||
logging.disable(logging.NOTSET if debug else logging.DEBUG)
|
||||
|
||||
|
||||
def is_debug():
|
||||
"""return debug flag"""
|
||||
return DEBUG
|
||||
|
||||
|
||||
def debug_watch(wrapped, instance, args, kwargs):
|
||||
"""For use with wrapt.wrap_function_wrapper to watch calls to a function"""
|
||||
caller = sys._getframe().f_back.f_code.co_name
|
||||
name = wrapped.__name__
|
||||
timestamp = datetime.now().isoformat()
|
||||
print(
|
||||
f"{timestamp} {name} called from {caller} with args: {args} and kwargs: {kwargs}"
|
||||
)
|
||||
start_t = time.perf_counter()
|
||||
rv = wrapped(*args, **kwargs)
|
||||
stop_t = time.perf_counter()
|
||||
print(f"{timestamp} {name} returned: {rv}, elapsed time: {stop_t - start_t} sec")
|
||||
return rv
|
||||
|
||||
|
||||
def debug_breakpoint(wrapped, instance, args, kwargs):
|
||||
"""For use with wrapt.wrap_function_wrapper to set breakpoint on a function"""
|
||||
breakpoint()
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
|
||||
def wrap_function(function_path, wrapper):
|
||||
"""Wrap a function with wrapper function"""
|
||||
module, name = function_path.split(".", 1)
|
||||
try:
|
||||
return wrapt.wrap_function_wrapper(module, name, wrapper)
|
||||
except AttributeError as e:
|
||||
raise AttributeError(f"{module}.{name} does not exist") from e
|
||||
|
||||
|
||||
def get_debug_options(arg_names: List, argv: List) -> Dict:
|
||||
"""Get the options for the debug options;
|
||||
Some of the debug options like --watch and --breakpoint need to be processed before any other packages are loaded
|
||||
so they can't be handled in the normal click argument processing, thus this function is called
|
||||
from osxphotos/cli/__init__.py
|
||||
|
||||
Assumes multi-valued options are OK and that all options take form of --option VALUE or --option=VALUE
|
||||
"""
|
||||
# argv[0] is the program name
|
||||
# argv[1] is the command
|
||||
# argv[2:] are the arguments
|
||||
args = {}
|
||||
for arg_name in arg_names:
|
||||
for idx, arg in enumerate(argv[1:]):
|
||||
if arg.startswith(f"{arg_name}="):
|
||||
arg_value = arg.split("=")[1]
|
||||
try:
|
||||
args[arg].append(arg_value)
|
||||
except KeyError:
|
||||
args[arg] = [arg_value]
|
||||
elif arg == arg_name:
|
||||
try:
|
||||
args[arg].append(argv[idx + 2])
|
||||
except KeyError:
|
||||
try:
|
||||
args[arg] = [argv[idx + 2]]
|
||||
except IndexError as e:
|
||||
raise ValueError(f"Missing value for {arg}") from e
|
||||
except IndexError as e:
|
||||
raise ValueError(f"Missing value for {arg}") from e
|
||||
return args
|
||||
|
||||
|
||||
def get_debug_flags(arg_names: List, argv: List) -> Dict:
|
||||
"""Get the flags for the debug options;
|
||||
Processes flags like --debug that resolve to True or False
|
||||
"""
|
||||
# argv[0] is the program name
|
||||
# argv[1] is the command
|
||||
# argv[2:] are the arguments
|
||||
args = {arg_name: False for arg_name in arg_names}
|
||||
for arg_name in arg_names:
|
||||
if arg_name in argv[1:]:
|
||||
args[arg_name] = True
|
||||
return args
|
||||
@@ -11,6 +11,7 @@ import html
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
@@ -19,11 +20,12 @@ from functools import lru_cache # pylint: disable=syntax-error
|
||||
|
||||
__all__ = [
|
||||
"escape_str",
|
||||
"unescape_str",
|
||||
"terminate_exiftool",
|
||||
"get_exiftool_path",
|
||||
"exiftool_can_write",
|
||||
"ExifTool",
|
||||
"ExifToolCaching",
|
||||
"get_exiftool_path",
|
||||
"terminate_exiftool",
|
||||
"unescape_str",
|
||||
]
|
||||
|
||||
# exiftool -stay_open commands outputs this EOF marker after command is run
|
||||
@@ -33,6 +35,24 @@ EXIFTOOL_STAYOPEN_EOF_LEN = len(EXIFTOOL_STAYOPEN_EOF)
|
||||
# list of exiftool processes to cleanup when exiting or when terminate is called
|
||||
EXIFTOOL_PROCESSES = []
|
||||
|
||||
# exiftool supported file types, created by utils/exiftool_supported_types.py
|
||||
EXIFTOOL_FILETYPES_JSON = "exiftool_filetypes.json"
|
||||
with (pathlib.Path(__file__).parent / EXIFTOOL_FILETYPES_JSON).open("r") as f:
|
||||
EXIFTOOL_SUPPORTED_FILETYPES = json.load(f)
|
||||
|
||||
|
||||
def exiftool_can_write(suffix: str) -> bool:
|
||||
"""Return True if exiftool supports writing to a file with the given suffix, otherwise False"""
|
||||
if not suffix:
|
||||
return False
|
||||
suffix = suffix.lower()
|
||||
if suffix[0] == ".":
|
||||
suffix = suffix[1:]
|
||||
return (
|
||||
suffix in EXIFTOOL_SUPPORTED_FILETYPES
|
||||
and EXIFTOOL_SUPPORTED_FILETYPES[suffix]["write"]
|
||||
)
|
||||
|
||||
|
||||
def escape_str(s):
|
||||
"""escape string for use with exiftool -E"""
|
||||
@@ -49,6 +69,8 @@ def unescape_str(s):
|
||||
"""unescape an HTML string returned by exiftool -E"""
|
||||
if type(s) != str:
|
||||
return s
|
||||
# avoid " in values which result in json.loads() throwing an exception, #636
|
||||
s = s.replace(""", '\\"')
|
||||
return html.unescape(s)
|
||||
|
||||
|
||||
@@ -85,7 +107,8 @@ class _ExifToolProc:
|
||||
|
||||
def __init__(self, exiftool=None):
|
||||
"""construct _ExifToolProc singleton object or return instance of already created object
|
||||
exiftool: optional path to exiftool binary (if not provided, will search path to find it)"""
|
||||
exiftool: optional path to exiftool binary (if not provided, will search path to find it)
|
||||
"""
|
||||
|
||||
if hasattr(self, "_process_running") and self._process_running:
|
||||
# already running
|
||||
@@ -95,7 +118,6 @@ class _ExifToolProc:
|
||||
f"ignoring exiftool={exiftool}"
|
||||
)
|
||||
return
|
||||
|
||||
self._process_running = False
|
||||
self._exiftool = exiftool or get_exiftool_path()
|
||||
self._start_proc()
|
||||
@@ -127,6 +149,9 @@ class _ExifToolProc:
|
||||
return
|
||||
|
||||
# open exiftool process
|
||||
# make sure /usr/bin at start of path so exiftool can find xattr (see #636)
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = f'/usr/bin/:{env["PATH"]}'
|
||||
self._process = subprocess.Popen(
|
||||
[
|
||||
self._exiftool,
|
||||
@@ -143,6 +168,7 @@ class _ExifToolProc:
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
env=env,
|
||||
)
|
||||
self._process_running = True
|
||||
|
||||
@@ -342,6 +368,7 @@ class ExifTool:
|
||||
error = "" if error == b"" else error.decode("utf-8")
|
||||
self.warning = warning
|
||||
self.error = error
|
||||
|
||||
return output[:-EXIFTOOL_STAYOPEN_EOF_LEN], warning, error
|
||||
|
||||
@property
|
||||
@@ -373,6 +400,7 @@ 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
|
||||
logging.warning(f"error loading json returned by exiftool: {e} {json_str}")
|
||||
return dict()
|
||||
exifdict = exifdict[0]
|
||||
if not tag_groups:
|
||||
|
||||
4976
osxphotos/exiftool_filetypes.json
Normal file
4976
osxphotos/exiftool_filetypes.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
255
osxphotos/export_db_utils.py
Normal file
255
osxphotos/export_db_utils.py
Normal file
@@ -0,0 +1,255 @@
|
||||
""" Utility functions for working with export_db """
|
||||
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import pathlib
|
||||
import sqlite3
|
||||
from typing import Callable, Optional, Tuple, Union
|
||||
|
||||
import toml
|
||||
from rich import print
|
||||
|
||||
from ._constants import OSXPHOTOS_EXPORT_DB
|
||||
from ._version import __version__
|
||||
from .utils import noop
|
||||
from .export_db import OSXPHOTOS_EXPORTDB_VERSION, ExportDB
|
||||
from .fileutil import FileUtil
|
||||
from .photosdb import PhotosDB
|
||||
|
||||
__all__ = [
|
||||
"export_db_check_signatures",
|
||||
"export_db_get_last_run",
|
||||
"export_db_get_version",
|
||||
"export_db_save_config_to_file",
|
||||
"export_db_touch_files",
|
||||
"export_db_update_signatures",
|
||||
"export_db_vacuum",
|
||||
]
|
||||
|
||||
|
||||
def isotime_from_ts(ts: int) -> str:
|
||||
"""Convert timestamp to ISO 8601 time string"""
|
||||
return datetime.datetime.fromtimestamp(ts).isoformat()
|
||||
|
||||
|
||||
def export_db_get_version(
|
||||
dbfile: Union[str, pathlib.Path]
|
||||
) -> Tuple[Optional[int], Optional[int]]:
|
||||
"""returns version from export database as tuple of (osxphotos version, export_db version)"""
|
||||
conn = sqlite3.connect(str(dbfile))
|
||||
c = conn.cursor()
|
||||
row = c.execute(
|
||||
"SELECT osxphotos, exportdb FROM version ORDER BY id DESC LIMIT 1;"
|
||||
).fetchone()
|
||||
if row:
|
||||
return (row[0], row[1])
|
||||
return (None, None)
|
||||
|
||||
|
||||
def export_db_vacuum(dbfile: Union[str, pathlib.Path]) -> None:
|
||||
"""Vacuum export database"""
|
||||
conn = sqlite3.connect(str(dbfile))
|
||||
c = conn.cursor()
|
||||
c.execute("VACUUM;")
|
||||
conn.commit()
|
||||
|
||||
|
||||
def export_db_update_signatures(
|
||||
dbfile: Union[str, pathlib.Path],
|
||||
export_dir: Union[str, pathlib.Path],
|
||||
verbose_: Callable = noop,
|
||||
dry_run: bool = False,
|
||||
) -> Tuple[int, int]:
|
||||
"""Update signatures for all files found in the export database to match what's on disk
|
||||
|
||||
Returns: tuple of (updated, skipped)
|
||||
"""
|
||||
export_dir = pathlib.Path(export_dir)
|
||||
fileutil = FileUtil
|
||||
conn = sqlite3.connect(str(dbfile))
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT filepath_normalized, filepath FROM export_data;")
|
||||
rows = c.fetchall()
|
||||
updated = 0
|
||||
skipped = 0
|
||||
for row in rows:
|
||||
filepath_normalized = row[0]
|
||||
filepath = row[1]
|
||||
filepath = export_dir / filepath
|
||||
if not os.path.exists(filepath):
|
||||
skipped += 1
|
||||
verbose_(f"[dark_orange]Skipping missing file[/dark_orange]: '{filepath}'")
|
||||
continue
|
||||
updated += 1
|
||||
file_sig = fileutil.file_sig(filepath)
|
||||
verbose_(f"[green]Updating signature for[/green]: '{filepath}'")
|
||||
if not dry_run:
|
||||
c.execute(
|
||||
"UPDATE export_data SET dest_mode = ?, dest_size = ?, dest_mtime = ? WHERE filepath_normalized = ?;",
|
||||
(file_sig[0], file_sig[1], file_sig[2], filepath_normalized),
|
||||
)
|
||||
|
||||
if not dry_run:
|
||||
conn.commit()
|
||||
|
||||
return (updated, skipped)
|
||||
|
||||
|
||||
def export_db_get_last_run(
|
||||
export_db: Union[str, pathlib.Path]
|
||||
) -> Tuple[Optional[str], Optional[str]]:
|
||||
"""Get last run from export database"""
|
||||
conn = sqlite3.connect(str(export_db))
|
||||
c = conn.cursor()
|
||||
row = c.execute(
|
||||
"SELECT datetime, args FROM runs ORDER BY id DESC LIMIT 1;"
|
||||
).fetchone()
|
||||
if row:
|
||||
return row[0], row[1]
|
||||
return None, None
|
||||
|
||||
|
||||
def export_db_save_config_to_file(
|
||||
export_db: Union[str, pathlib.Path], config_file: Union[str, pathlib.Path]
|
||||
) -> None:
|
||||
"""Save export_db last run config to file"""
|
||||
export_db = pathlib.Path(export_db)
|
||||
config_file = pathlib.Path(config_file)
|
||||
conn = sqlite3.connect(str(export_db))
|
||||
c = conn.cursor()
|
||||
row = c.execute("SELECT config FROM config ORDER BY id DESC LIMIT 1;").fetchone()
|
||||
if not row:
|
||||
return ValueError("No config found in export_db")
|
||||
with config_file.open("w") as f:
|
||||
f.write(row[0])
|
||||
|
||||
|
||||
def export_db_check_signatures(
|
||||
dbfile: Union[str, pathlib.Path],
|
||||
export_dir: Union[str, pathlib.Path],
|
||||
verbose_: Callable = noop,
|
||||
) -> Tuple[int, int, int]:
|
||||
"""Check signatures for all files found in the export database to verify what matches the on disk files
|
||||
|
||||
Returns: tuple of (updated, skipped)
|
||||
"""
|
||||
export_dir = pathlib.Path(export_dir)
|
||||
fileutil = FileUtil
|
||||
conn = sqlite3.connect(str(dbfile))
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT filepath_normalized, filepath FROM export_data;")
|
||||
rows = c.fetchall()
|
||||
exportdb = ExportDB(dbfile, export_dir)
|
||||
matched = 0
|
||||
notmatched = 0
|
||||
skipped = 0
|
||||
for row in rows:
|
||||
filepath_normalized = row[0]
|
||||
filepath = row[1]
|
||||
filepath = export_dir / filepath
|
||||
if not filepath.exists():
|
||||
skipped += 1
|
||||
verbose_(f"[dark_orange]Skipping missing file[/dark_orange]: '{filepath}'")
|
||||
continue
|
||||
file_sig = fileutil.file_sig(filepath)
|
||||
file_rec = exportdb.get_file_record(filepath)
|
||||
if file_rec.dest_sig == file_sig:
|
||||
matched += 1
|
||||
verbose_(f"[green]Signatures matched[/green]: '{filepath}'")
|
||||
else:
|
||||
notmatched += 1
|
||||
verbose_(f"[deep_pink3]Signatures do not match[/deep_pink3]: '{filepath}'")
|
||||
|
||||
return (matched, notmatched, skipped)
|
||||
|
||||
|
||||
def export_db_touch_files(
|
||||
dbfile: Union[str, pathlib.Path],
|
||||
export_dir: Union[str, pathlib.Path],
|
||||
verbose_: Callable = noop,
|
||||
dry_run: bool = False,
|
||||
) -> Tuple[int, int, int]:
|
||||
"""Touch files on disk to match the Photos library created date
|
||||
|
||||
Returns: tuple of (touched, not_touched, skipped)
|
||||
"""
|
||||
export_dir = pathlib.Path(export_dir)
|
||||
|
||||
# open and close exportdb to ensure it gets migrated
|
||||
exportdb = ExportDB(dbfile, export_dir)
|
||||
upgraded = exportdb.was_upgraded
|
||||
if upgraded:
|
||||
verbose_(
|
||||
f"Upgraded export database {dbfile} from version {upgraded[0]} to {upgraded[1]}"
|
||||
)
|
||||
exportdb.close()
|
||||
|
||||
conn = sqlite3.connect(str(dbfile))
|
||||
c = conn.cursor()
|
||||
# get most recent config
|
||||
row = c.execute("SELECT config FROM config ORDER BY id DESC LIMIT 1;").fetchone()
|
||||
if row:
|
||||
config = toml.loads(row[0])
|
||||
try:
|
||||
photos_db_path = config["export"].get("db", None)
|
||||
except KeyError:
|
||||
photos_db_path = None
|
||||
else:
|
||||
# TODO: parse the runs table to get the last --db
|
||||
# in the mean time, photos_db_path = None will use the default library
|
||||
photos_db_path = None
|
||||
|
||||
photosdb = PhotosDB(dbfile=photos_db_path, verbose=verbose_)
|
||||
exportdb = ExportDB(dbfile, export_dir)
|
||||
c.execute(
|
||||
"SELECT filepath_normalized, filepath, uuid, dest_mode, dest_size FROM export_data;"
|
||||
)
|
||||
rows = c.fetchall()
|
||||
touched = 0
|
||||
not_touched = 0
|
||||
skipped = 0
|
||||
for row in rows:
|
||||
filepath_normalized = row[0]
|
||||
filepath = row[1]
|
||||
filepath = export_dir / filepath
|
||||
uuid = row[2]
|
||||
dest_mode = row[3]
|
||||
dest_size = row[4]
|
||||
if not filepath.exists():
|
||||
skipped += 1
|
||||
verbose_(
|
||||
f"[dark_orange]Skipping missing file (not in export directory)[/dark_orange]: '{filepath}'"
|
||||
)
|
||||
continue
|
||||
|
||||
photo = photosdb.get_photo(uuid)
|
||||
if not photo:
|
||||
skipped += 1
|
||||
verbose_(
|
||||
f"[dark_orange]Skipping missing photo (did not find in Photos Library)[/dark_orange]: '{filepath}' ({uuid})"
|
||||
)
|
||||
continue
|
||||
|
||||
ts = int(photo.date.timestamp())
|
||||
stat = os.stat(str(filepath))
|
||||
mtime = stat.st_mtime
|
||||
if mtime == ts:
|
||||
not_touched += 1
|
||||
verbose_(
|
||||
f"[green]Skipping file (timestamp matches)[/green]: '{filepath}' [dodger_blue1]{isotime_from_ts(ts)} ({ts})[/dodger_blue1]"
|
||||
)
|
||||
continue
|
||||
|
||||
touched += 1
|
||||
verbose_(
|
||||
f"[deep_pink3]Touching file[/deep_pink3]: '{filepath}' "
|
||||
f"[dodger_blue1]{isotime_from_ts(mtime)} ({mtime}) -> {isotime_from_ts(ts)} ({ts})[/dodger_blue1]"
|
||||
)
|
||||
|
||||
if not dry_run:
|
||||
os.utime(str(filepath), (ts, ts))
|
||||
rec = exportdb.get_file_record(filepath)
|
||||
rec.dest_sig = (dest_mode, dest_size, ts)
|
||||
|
||||
return (touched, not_touched, skipped)
|
||||
@@ -3,9 +3,10 @@
|
||||
import os
|
||||
import pathlib
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import typing as t
|
||||
from abc import ABC, abstractmethod
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -67,6 +68,13 @@ class FileUtilABC(ABC):
|
||||
def rename(cls, src, dest):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def tmpdir(
|
||||
cls, prefix: t.Optional[str] = None, dir: t.Optional[str] = None
|
||||
) -> tempfile.TemporaryDirectory:
|
||||
pass
|
||||
|
||||
|
||||
class FileUtilMacOS(FileUtilABC):
|
||||
"""Various file utilities"""
|
||||
@@ -84,11 +92,10 @@ class FileUtilMacOS(FileUtilABC):
|
||||
if not os.path.isfile(src):
|
||||
raise FileNotFoundError("src file does not appear to exist", src)
|
||||
|
||||
# if error on copy, subprocess will raise CalledProcessError
|
||||
try:
|
||||
os.link(src, dest)
|
||||
except Exception as e:
|
||||
raise e
|
||||
raise e from e
|
||||
|
||||
@classmethod
|
||||
def copy(cls, src, dest):
|
||||
@@ -143,7 +150,7 @@ class FileUtilMacOS(FileUtilABC):
|
||||
@classmethod
|
||||
def utime(cls, path, times):
|
||||
"""Set the access and modified time of path."""
|
||||
os.utime(path, times)
|
||||
os.utime(path, times=times)
|
||||
|
||||
@classmethod
|
||||
def cmp(cls, f1, f2, mtime1=None):
|
||||
@@ -187,7 +194,7 @@ class FileUtilMacOS(FileUtilABC):
|
||||
|
||||
@classmethod
|
||||
def file_sig(cls, f1):
|
||||
"""return os.stat signature for file f1"""
|
||||
"""return os.stat signature for file f1 as tuple of (mode, size, mtime)"""
|
||||
return cls._sig(os.stat(f1))
|
||||
|
||||
@classmethod
|
||||
@@ -222,6 +229,17 @@ class FileUtilMacOS(FileUtilABC):
|
||||
os.rename(str(src), str(dest))
|
||||
return dest
|
||||
|
||||
@classmethod
|
||||
def tmpdir(
|
||||
cls, prefix: t.Optional[str] = None, dir: t.Optional[str] = None
|
||||
) -> tempfile.TemporaryDirectory:
|
||||
"""Securely creates a temporary directory using the same rules as mkdtemp().
|
||||
The resulting object can be used as a context manager.
|
||||
On completion of the context or destruction of the temporary directory object,
|
||||
the newly created temporary directory and all its contents are removed from the filesystem.
|
||||
"""
|
||||
return TemporaryDirectory(prefix=prefix, dir=dir)
|
||||
|
||||
@staticmethod
|
||||
def _sig(st):
|
||||
"""return tuple of (mode, size, mtime) of file based on os.stat
|
||||
@@ -240,7 +258,7 @@ class FileUtil(FileUtilMacOS):
|
||||
|
||||
class FileUtilNoOp(FileUtil):
|
||||
"""No-Op implementation of FileUtil for testing / dry-run mode
|
||||
all methods with exception of cmp, cmp_file_sig and file_cmp are no-op
|
||||
all methods with exception of tmpdir, cmp, cmp_file_sig and file_cmp are no-op
|
||||
cmp and cmp_file_sig functions as FileUtil methods do
|
||||
file_cmp returns mock data
|
||||
"""
|
||||
@@ -249,8 +267,6 @@ class FileUtilNoOp(FileUtil):
|
||||
def noop(*args):
|
||||
pass
|
||||
|
||||
verbose = noop
|
||||
|
||||
def __new__(cls, verbose=None):
|
||||
if verbose:
|
||||
if callable(verbose):
|
||||
@@ -261,33 +277,43 @@ class FileUtilNoOp(FileUtil):
|
||||
|
||||
@classmethod
|
||||
def hardlink(cls, src, dest):
|
||||
cls.verbose(f"hardlink: {src} {dest}")
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def copy(cls, src, dest, norsrc=False):
|
||||
cls.verbose(f"copy: {src} {dest}")
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def unlink(cls, dest):
|
||||
cls.verbose(f"unlink: {dest}")
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def rmdir(cls, dest):
|
||||
cls.verbose(f"rmdir: {dest}")
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def utime(cls, path, times):
|
||||
cls.verbose(f"utime: {path}, {times}")
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def file_sig(cls, file1):
|
||||
cls.verbose(f"file_sig: {file1}")
|
||||
return (42, 42, 42)
|
||||
|
||||
@classmethod
|
||||
def convert_to_jpeg(cls, src_file, dest_file, compression_quality=1.0):
|
||||
cls.verbose(f"convert_to_jpeg: {src_file}, {dest_file}, {compression_quality}")
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def rename(cls, src, dest):
|
||||
cls.verbose(f"rename: {src}, {dest}")
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def tmpdir(
|
||||
cls, prefix: t.Optional[str] = None, dir: t.Optional[str] = None
|
||||
) -> tempfile.TemporaryDirectory:
|
||||
"""Securely creates a temporary directory using the same rules as mkdtemp().
|
||||
The resulting object can be used as a context manager.
|
||||
On completion of the context or destruction of the temporary directory object,
|
||||
the newly created temporary directory and all its contents are removed from the filesystem.
|
||||
"""
|
||||
return TemporaryDirectory(prefix=prefix, dir=dir)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -54,7 +54,7 @@ from .scoreinfo import ScoreInfo
|
||||
from .searchinfo import SearchInfo
|
||||
from .text_detection import detect_text
|
||||
from .uti import get_preferred_uti_extension, get_uti_for_extension
|
||||
from .utils import _debug, _get_resource_loc, findfiles
|
||||
from .utils import _get_resource_loc, list_directory
|
||||
|
||||
__all__ = ["PhotoInfo", "PhotoInfoNone"]
|
||||
|
||||
@@ -369,7 +369,7 @@ class PhotoInfo:
|
||||
# In Photos 5, raw is in same folder as original but with _4.ext
|
||||
# Unless "Copy Items to the Photos Library" is not checked
|
||||
# then RAW image is not renamed but has same name is jpeg buth with raw extension
|
||||
# Current implementation uses findfiles to find images with the correct raw UTI extension
|
||||
# Current implementation finds images with the correct raw UTI extension
|
||||
# in same folder as the original and with same stem as original in form: original_stem*.raw_ext
|
||||
# TODO: I don't like this -- would prefer a more deterministic approach but until I have more
|
||||
# data on how Photos stores and retrieves RAW images, this seems to be working
|
||||
@@ -405,8 +405,7 @@ class PhotoInfo:
|
||||
# raw files have same name as original but with _4.raw_ext appended
|
||||
# I believe the _4 maps to PHAssetResourceTypeAlternatePhoto = 4
|
||||
# see: https://developer.apple.com/documentation/photokit/phassetresourcetype/phassetresourcetypealternatephoto?language=objc
|
||||
glob_str = f"{filestem}_4*"
|
||||
raw_file = findfiles(glob_str, filepath)
|
||||
raw_file = list_directory(filepath, startswith=f"{filestem}_4")
|
||||
if not raw_file:
|
||||
photopath = None
|
||||
else:
|
||||
@@ -589,6 +588,7 @@ class PhotoInfo:
|
||||
@property
|
||||
def ismissing(self):
|
||||
"""returns true if photo is missing from disk (which means it's not been downloaded from iCloud)
|
||||
|
||||
NOTE: the photos.db database uses an asynchrounous write-ahead log so changes in Photos
|
||||
do not immediately get written to disk. In particular, I've noticed that downloading
|
||||
an image from the cloud does not force the database to be updated until something else
|
||||
@@ -1489,7 +1489,8 @@ class PhotoInfo:
|
||||
description_template: string; optional template string that will be rendered for use as photo description
|
||||
render_options: an optional osxphotos.phototemplate.RenderOptions instance with options to pass to template renderer
|
||||
|
||||
Returns: list of photos exported
|
||||
Returns:
|
||||
list of photos exported
|
||||
"""
|
||||
|
||||
exporter = PhotoExporter(self)
|
||||
@@ -1729,7 +1730,11 @@ class PhotoInfo:
|
||||
if isinstance(o, (datetime.date, datetime.datetime)):
|
||||
return o.isoformat()
|
||||
|
||||
return json.dumps(self.asdict(), sort_keys=True, default=default)
|
||||
dict_data = self.asdict()
|
||||
for k, v in dict_data.items():
|
||||
if v and isinstance(v, (list, tuple)) and not isinstance(v[0], dict):
|
||||
dict_data[k] = sorted(v)
|
||||
return json.dumps(dict_data, sort_keys=True, default=default)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Compare two PhotoInfo objects for equality"""
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import logging
|
||||
|
||||
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION
|
||||
from ..utils import _db_is_locked, _debug, _open_sql_file
|
||||
from ..utils import _db_is_locked, _open_sql_file
|
||||
from .photosdb_utils import get_db_version
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import uuid as uuidlib
|
||||
from pprint import pformat
|
||||
|
||||
from .._constants import _PHOTOS_4_VERSION, SEARCH_CATEGORY_LABEL
|
||||
from ..utils import _db_is_locked, _debug, _open_sql_file, normalize_unicode
|
||||
from ..utils import _db_is_locked, _open_sql_file, normalize_unicode
|
||||
|
||||
"""
|
||||
This module should be imported in the class defintion of PhotosDB in photosdb.py
|
||||
@@ -139,17 +139,6 @@ def _process_searchinfo(self):
|
||||
_db_searchinfo_labels[label] = [uuid]
|
||||
_db_searchinfo_labels_normalized[label_norm] = [uuid]
|
||||
|
||||
if _debug():
|
||||
logging.debug(
|
||||
"_db_searchinfo_categories: \n" + pformat(self._db_searchinfo_categories)
|
||||
)
|
||||
logging.debug("_db_searchinfo_uuid: \n" + pformat(self._db_searchinfo_uuid))
|
||||
logging.debug("_db_searchinfo_labels: \n" + pformat(self._db_searchinfo_labels))
|
||||
logging.debug(
|
||||
"_db_searchinfo_labels_normalized: \n"
|
||||
+ pformat(self._db_searchinfo_labels_normalized)
|
||||
)
|
||||
|
||||
conn.close()
|
||||
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ from .._constants import (
|
||||
_PHOTOS_5_PROJECT_ALBUM_KIND,
|
||||
_PHOTOS_5_ROOT_FOLDER_KIND,
|
||||
_PHOTOS_5_SHARED_ALBUM_KIND,
|
||||
_PHOTOS_5_VERSION,
|
||||
_TESTED_OS_VERSIONS,
|
||||
_UNKNOWN_PERSON,
|
||||
BURST_KEY,
|
||||
@@ -49,15 +50,16 @@ from .._constants import (
|
||||
from .._version import __version__
|
||||
from ..albuminfo import AlbumInfo, FolderInfo, ImportInfo, ProjectInfo
|
||||
from ..datetime_utils import datetime_has_tz, datetime_naive_to_local
|
||||
from ..debug import is_debug
|
||||
from ..fileutil import FileUtil
|
||||
from ..personinfo import PersonInfo
|
||||
from ..photoinfo import PhotoInfo
|
||||
from ..phototemplate import RenderOptions
|
||||
from ..queryoptions import QueryOptions
|
||||
from ..rich_utils import add_rich_markup_tag
|
||||
from ..utils import (
|
||||
_check_file_exists,
|
||||
_db_is_locked,
|
||||
_debug,
|
||||
_get_os_version,
|
||||
_open_sql_file,
|
||||
get_last_library_path,
|
||||
@@ -89,13 +91,14 @@ class PhotosDB:
|
||||
labels_normalized_as_dict,
|
||||
)
|
||||
|
||||
def __init__(self, dbfile=None, verbose=None, exiftool=None):
|
||||
def __init__(self, dbfile=None, verbose=None, exiftool=None, rich=None):
|
||||
"""Create a new PhotosDB object.
|
||||
|
||||
Args:
|
||||
dbfile: specify full path to photos library or photos.db; if None, will attempt to locate last library opened by Photos.
|
||||
verbose: optional callable function to use for printing verbose text during processing; if None (default), does not print output.
|
||||
exiftool: optional path to exiftool for methods that require this (e.g. PhotoInfo.exiftool); if not provided, will search PATH
|
||||
rich: use rich with verbose output
|
||||
|
||||
Raises:
|
||||
FileNotFoundError if dbfile is not a valid Photos library.
|
||||
@@ -118,6 +121,12 @@ class PhotosDB:
|
||||
raise TypeError("verbose must be callable")
|
||||
self._verbose = verbose
|
||||
|
||||
# define functions for adding markup
|
||||
self._filepath = add_rich_markup_tag("filepath", rich=rich)
|
||||
self._filename = add_rich_markup_tag("filename", rich=rich)
|
||||
self._uuid = add_rich_markup_tag("uuid", rich=rich)
|
||||
self._num = add_rich_markup_tag("num", rich=rich)
|
||||
|
||||
# enable beta features
|
||||
self._beta = False
|
||||
|
||||
@@ -263,7 +272,7 @@ class PhotosDB:
|
||||
# key is Z_PK of ZMOMENT table and values are the moment info
|
||||
self._db_moment_pk = {}
|
||||
|
||||
if _debug():
|
||||
if is_debug():
|
||||
logging.debug(f"dbfile = {dbfile}")
|
||||
|
||||
if dbfile is None:
|
||||
@@ -280,7 +289,7 @@ class PhotosDB:
|
||||
if not _check_file_exists(dbfile):
|
||||
raise FileNotFoundError(f"dbfile {dbfile} does not exist", dbfile)
|
||||
|
||||
if _debug():
|
||||
if is_debug():
|
||||
logging.debug(f"dbfile = {dbfile}")
|
||||
|
||||
# init database names
|
||||
@@ -294,7 +303,7 @@ class PhotosDB:
|
||||
# or photosanalysisd
|
||||
self._dbfile = self._dbfile_actual = self._tmp_db = os.path.abspath(dbfile)
|
||||
|
||||
verbose(f"Processing database {self._dbfile}")
|
||||
verbose(f"Processing database {self._filepath(self._dbfile)}")
|
||||
|
||||
# if database is exclusively locked, make a copy of it and use the copy
|
||||
# Photos maintains an exclusive lock on the database file while Photos is open
|
||||
@@ -314,13 +323,13 @@ class PhotosDB:
|
||||
raise FileNotFoundError(f"dbfile {dbfile} does not exist", dbfile)
|
||||
else:
|
||||
self._dbfile_actual = self._tmp_db = dbfile
|
||||
verbose(f"Processing database {self._dbfile_actual}")
|
||||
verbose(f"Processing database {self._filepath(self._dbfile_actual)}")
|
||||
# if database is exclusively locked, make a copy of it and use the copy
|
||||
if _db_is_locked(self._dbfile_actual):
|
||||
verbose(f"Database locked, creating temporary copy.")
|
||||
self._tmp_db = self._copy_db_file(self._dbfile_actual)
|
||||
|
||||
if _debug():
|
||||
if is_debug():
|
||||
logging.debug(
|
||||
f"_dbfile = {self._dbfile}, _dbfile_actual = {self._dbfile_actual}"
|
||||
)
|
||||
@@ -335,7 +344,7 @@ class PhotosDB:
|
||||
masters_path = os.path.join(library_path, "originals")
|
||||
self._masters_path = masters_path
|
||||
|
||||
if _debug():
|
||||
if is_debug():
|
||||
logging.debug(f"library = {library_path}, masters = {masters_path}")
|
||||
|
||||
if int(self._db_version) <= int(_PHOTOS_4_VERSION):
|
||||
@@ -591,7 +600,7 @@ class PhotosDB:
|
||||
print(f"Error copying{fname} to {dest_path}", file=sys.stderr)
|
||||
raise Exception
|
||||
|
||||
if _debug():
|
||||
if is_debug():
|
||||
logging.debug(dest_path)
|
||||
|
||||
return dest_path
|
||||
@@ -618,7 +627,7 @@ class PhotosDB:
|
||||
# print("Error linking " + fname + " to " + dest_path, file=sys.stderr)
|
||||
# raise Exception
|
||||
|
||||
# if _debug():
|
||||
# if is_debug():
|
||||
# logging.debug(dest_path)
|
||||
|
||||
# return dest_path
|
||||
@@ -629,7 +638,7 @@ class PhotosDB:
|
||||
|
||||
verbose = self._verbose
|
||||
verbose("Processing database.")
|
||||
verbose(f"Database version: {self._db_version}.")
|
||||
verbose(f"Database version: {self._num(self._db_version)}.")
|
||||
|
||||
self._photos_ver = 4 # only used in Photos 5+
|
||||
|
||||
@@ -659,14 +668,18 @@ class PhotosDB:
|
||||
|
||||
for person in c:
|
||||
pk = person[0]
|
||||
fullname = person[2] if person[2] is not None else _UNKNOWN_PERSON
|
||||
fullname = (
|
||||
normalize_unicode(person[2])
|
||||
if person[2] is not None
|
||||
else _UNKNOWN_PERSON
|
||||
)
|
||||
self._dbpersons_pk[pk] = {
|
||||
"pk": pk,
|
||||
"uuid": person[1],
|
||||
"fullname": fullname,
|
||||
"facecount": person[3],
|
||||
"keyface": person[5],
|
||||
"displayname": person[4],
|
||||
"displayname": normalize_unicode(person[4]),
|
||||
"photo_uuid": None,
|
||||
"keyface_uuid": None,
|
||||
}
|
||||
@@ -733,13 +746,6 @@ class PhotosDB:
|
||||
except KeyError:
|
||||
self._dbfaces_pk[pk] = [uuid]
|
||||
|
||||
if _debug():
|
||||
logging.debug(f"Finished walking through persons")
|
||||
logging.debug(pformat(self._dbpersons_pk))
|
||||
logging.debug(pformat(self._dbpersons_fullname))
|
||||
logging.debug(pformat(self._dbfaces_pk))
|
||||
logging.debug(pformat(self._dbfaces_uuid))
|
||||
|
||||
# Get info on albums
|
||||
verbose("Processing albums.")
|
||||
c.execute(
|
||||
@@ -876,14 +882,6 @@ class PhotosDB:
|
||||
else:
|
||||
self._dbalbum_folders[album] = {}
|
||||
|
||||
if _debug():
|
||||
logging.debug(f"Finished walking through albums")
|
||||
logging.debug(pformat(self._dbalbums_album))
|
||||
logging.debug(pformat(self._dbalbums_uuid))
|
||||
logging.debug(pformat(self._dbalbum_details))
|
||||
logging.debug(pformat(self._dbalbum_folders))
|
||||
logging.debug(pformat(self._dbfolder_details))
|
||||
|
||||
# Get info on keywords
|
||||
verbose("Processing keywords.")
|
||||
c.execute(
|
||||
@@ -899,13 +897,16 @@ class PhotosDB:
|
||||
RKMaster.uuid = RKVersion.masterUuid
|
||||
"""
|
||||
)
|
||||
for keyword in c:
|
||||
if not keyword[1] in self._dbkeywords_uuid:
|
||||
self._dbkeywords_uuid[keyword[1]] = []
|
||||
if not keyword[0] in self._dbkeywords_keyword:
|
||||
self._dbkeywords_keyword[keyword[0]] = []
|
||||
self._dbkeywords_uuid[keyword[1]].append(keyword[0])
|
||||
self._dbkeywords_keyword[keyword[0]].append(keyword[1])
|
||||
for keyword_title, keyword_uuid, _ in c:
|
||||
keyword_title = normalize_unicode(keyword_title)
|
||||
try:
|
||||
self._dbkeywords_uuid[keyword_uuid].append(keyword_title)
|
||||
except KeyError:
|
||||
self._dbkeywords_uuid[keyword_uuid] = [keyword_title]
|
||||
try:
|
||||
self._dbkeywords_keyword[keyword_title].append(keyword_uuid)
|
||||
except KeyError:
|
||||
self._dbkeywords_keyword[keyword_title] = [keyword_uuid]
|
||||
|
||||
# Get info on disk volumes
|
||||
c.execute("select RKVolume.modelId, RKVolume.name from RKVolume")
|
||||
@@ -1027,13 +1028,11 @@ class PhotosDB:
|
||||
|
||||
for row in c:
|
||||
uuid = row[0]
|
||||
if _debug():
|
||||
logging.debug(f"uuid = '{uuid}, master = '{row[2]}")
|
||||
self._dbphotos[uuid] = {}
|
||||
self._dbphotos[uuid]["_uuid"] = uuid # stored here for easier debugging
|
||||
self._dbphotos[uuid]["modelID"] = row[1]
|
||||
self._dbphotos[uuid]["masterUuid"] = row[2]
|
||||
self._dbphotos[uuid]["filename"] = row[3]
|
||||
self._dbphotos[uuid]["filename"] = normalize_unicode(row[3])
|
||||
|
||||
# There are sometimes negative values for lastmodifieddate in the database
|
||||
# I don't know what these mean but they will raise exception in datetime if
|
||||
@@ -1088,7 +1087,7 @@ class PhotosDB:
|
||||
self._dbphotos[uuid]["type"] = _MOVIE_TYPE
|
||||
else:
|
||||
# unknown
|
||||
if _debug():
|
||||
if is_debug():
|
||||
logging.debug(f"WARNING: {uuid} found unknown type {row[21]}")
|
||||
self._dbphotos[uuid]["type"] = None
|
||||
|
||||
@@ -1272,13 +1271,13 @@ class PhotosDB:
|
||||
info["volumeId"] = row[1]
|
||||
info["imagePath"] = row[2]
|
||||
info["isMissing"] = row[3]
|
||||
info["originalFilename"] = row[4]
|
||||
info["originalFilename"] = normalize_unicode(row[4])
|
||||
info["UTI"] = row[5]
|
||||
info["modelID"] = row[6]
|
||||
info["fileSize"] = row[7]
|
||||
info["isTrulyRAW"] = row[8]
|
||||
info["alternateMasterUuid"] = row[9]
|
||||
info["filename"] = row[10]
|
||||
info["filename"] = normalize_unicode(row[10])
|
||||
self._dbphotos_master[uuid] = info
|
||||
|
||||
# get details needed to find path of the edited photos
|
||||
@@ -1311,7 +1310,7 @@ class PhotosDB:
|
||||
and row[6] == 2
|
||||
):
|
||||
if "edit_resource_id" in self._dbphotos[uuid]:
|
||||
if _debug():
|
||||
if is_debug():
|
||||
logging.debug(
|
||||
f"WARNING: found more than one edit_resource_id for "
|
||||
f"UUID {row[0]},adjustmentUUID {row[1]}, modelID {row[2]}"
|
||||
@@ -1550,39 +1549,6 @@ class PhotosDB:
|
||||
|
||||
# done processing, dump debug data if requested
|
||||
verbose("Done processing details from Photos library.")
|
||||
if _debug():
|
||||
logging.debug("Faces (_dbfaces_uuid):")
|
||||
logging.debug(pformat(self._dbfaces_uuid))
|
||||
|
||||
logging.debug("Persons (_dbpersons_pk):")
|
||||
logging.debug(pformat(self._dbpersons_pk))
|
||||
|
||||
logging.debug("Keywords by uuid (_dbkeywords_uuid):")
|
||||
logging.debug(pformat(self._dbkeywords_uuid))
|
||||
|
||||
logging.debug("Keywords by keyword (_dbkeywords_keywords):")
|
||||
logging.debug(pformat(self._dbkeywords_keyword))
|
||||
|
||||
logging.debug("Albums by uuid (_dbalbums_uuid):")
|
||||
logging.debug(pformat(self._dbalbums_uuid))
|
||||
|
||||
logging.debug("Albums by album (_dbalbums_albums):")
|
||||
logging.debug(pformat(self._dbalbums_album))
|
||||
|
||||
logging.debug("Album details (_dbalbum_details):")
|
||||
logging.debug(pformat(self._dbalbum_details))
|
||||
|
||||
logging.debug("Album titles (_dbalbum_titles):")
|
||||
logging.debug(pformat(self._dbalbum_titles))
|
||||
|
||||
logging.debug("Volumes (_dbvolumes):")
|
||||
logging.debug(pformat(self._dbvolumes))
|
||||
|
||||
logging.debug("Photos (_dbphotos):")
|
||||
logging.debug(pformat(self._dbphotos))
|
||||
|
||||
logging.debug("Burst Photos (dbphotos_burst:")
|
||||
logging.debug(pformat(self._dbphotos_burst))
|
||||
|
||||
def _build_album_folder_hierarchy_4(self, uuid, folders=None):
|
||||
"""recursively build folder/album hierarchy
|
||||
@@ -1623,7 +1589,7 @@ class PhotosDB:
|
||||
but it works so don't touch it.
|
||||
"""
|
||||
|
||||
if _debug():
|
||||
if is_debug():
|
||||
logging.debug(f"_process_database5")
|
||||
verbose = self._verbose
|
||||
verbose(f"Processing database.")
|
||||
@@ -1632,7 +1598,9 @@ class PhotosDB:
|
||||
# some of the tables/columns have different names in different versions of Photos
|
||||
photos_ver = get_db_model_version(self._tmp_db)
|
||||
self._photos_ver = photos_ver
|
||||
verbose(f"Database version: {self._db_version}, {photos_ver}.")
|
||||
verbose(
|
||||
f"Database version: {self._num(self._db_version)}, {self._num(photos_ver)}."
|
||||
)
|
||||
asset_table = _DB_TABLE_NAMES[photos_ver]["ASSET"]
|
||||
keyword_join = _DB_TABLE_NAMES[photos_ver]["KEYWORD_JOIN"]
|
||||
asset_album_table = _DB_TABLE_NAMES[photos_ver]["ASSET_ALBUM_TABLE"]
|
||||
@@ -1645,7 +1613,7 @@ class PhotosDB:
|
||||
hdr_type_column = _DB_TABLE_NAMES[photos_ver]["HDR_TYPE"]
|
||||
|
||||
# Look for all combinations of persons and pictures
|
||||
if _debug():
|
||||
if is_debug():
|
||||
logging.debug(f"Getting information about persons")
|
||||
|
||||
# get info to associate persons with photos
|
||||
@@ -1673,7 +1641,7 @@ class PhotosDB:
|
||||
for person in c:
|
||||
pk = person[0]
|
||||
fullname = (
|
||||
person[2]
|
||||
normalize_unicode(person[2])
|
||||
if (person[2] != "" and person[2] is not None)
|
||||
else _UNKNOWN_PERSON
|
||||
)
|
||||
@@ -1683,7 +1651,7 @@ class PhotosDB:
|
||||
"fullname": fullname,
|
||||
"facecount": person[3],
|
||||
"keyface": person[4],
|
||||
"displayname": person[5],
|
||||
"displayname": normalize_unicode(person[5]),
|
||||
"photo_uuid": None,
|
||||
"keyface_uuid": None,
|
||||
}
|
||||
@@ -1747,13 +1715,6 @@ class PhotosDB:
|
||||
except KeyError:
|
||||
self._dbfaces_pk[pk] = [uuid]
|
||||
|
||||
if _debug():
|
||||
logging.debug(f"Finished walking through persons")
|
||||
logging.debug(pformat(self._dbpersons_pk))
|
||||
logging.debug(pformat(self._dbpersons_fullname))
|
||||
logging.debug(pformat(self._dbfaces_pk))
|
||||
logging.debug(pformat(self._dbfaces_uuid))
|
||||
|
||||
# get details about albums
|
||||
verbose("Processing albums.")
|
||||
c.execute(
|
||||
@@ -1870,13 +1831,6 @@ class PhotosDB:
|
||||
# shared albums can't be in folders
|
||||
self._dbalbum_folders[album] = []
|
||||
|
||||
if _debug():
|
||||
logging.debug(f"Finished walking through albums")
|
||||
logging.debug(pformat(self._dbalbums_album))
|
||||
logging.debug(pformat(self._dbalbums_uuid))
|
||||
logging.debug(pformat(self._dbalbum_details))
|
||||
logging.debug(pformat(self._dbalbum_folders))
|
||||
|
||||
# get details on keywords
|
||||
verbose("Processing keywords.")
|
||||
c.execute(
|
||||
@@ -1886,29 +1840,22 @@ class PhotosDB:
|
||||
JOIN Z_1KEYWORDS ON Z_1KEYWORDS.Z_1ASSETATTRIBUTES = ZADDITIONALASSETATTRIBUTES.Z_PK
|
||||
JOIN ZKEYWORD ON ZKEYWORD.Z_PK = {keyword_join} """
|
||||
)
|
||||
for keyword in c:
|
||||
keyword_title = normalize_unicode(keyword[0])
|
||||
if not keyword[1] in self._dbkeywords_uuid:
|
||||
self._dbkeywords_uuid[keyword[1]] = []
|
||||
if not keyword_title in self._dbkeywords_keyword:
|
||||
self._dbkeywords_keyword[keyword_title] = []
|
||||
self._dbkeywords_uuid[keyword[1]].append(keyword[0])
|
||||
self._dbkeywords_keyword[keyword_title].append(keyword[1])
|
||||
|
||||
if _debug():
|
||||
logging.debug(f"Finished walking through keywords")
|
||||
logging.debug(pformat(self._dbkeywords_keyword))
|
||||
logging.debug(pformat(self._dbkeywords_uuid))
|
||||
for keyword_title, keyword_uuid in c:
|
||||
keyword_title = normalize_unicode(keyword_title)
|
||||
try:
|
||||
self._dbkeywords_uuid[keyword_uuid].append(keyword_title)
|
||||
except KeyError:
|
||||
self._dbkeywords_uuid[keyword_uuid] = [keyword_title]
|
||||
try:
|
||||
self._dbkeywords_keyword[keyword_title].append(keyword_uuid)
|
||||
except KeyError:
|
||||
self._dbkeywords_keyword[keyword_title] = [keyword_uuid]
|
||||
|
||||
# get details on disk volumes
|
||||
c.execute("SELECT ZUUID, ZNAME from ZFILESYSTEMVOLUME")
|
||||
for vol in c:
|
||||
self._dbvolumes[vol[0]] = vol[1]
|
||||
|
||||
if _debug():
|
||||
logging.debug(f"Finished walking through volumes")
|
||||
logging.debug(self._dbvolumes)
|
||||
|
||||
# get details about photos
|
||||
verbose("Processing photo details.")
|
||||
c.execute(
|
||||
@@ -2042,8 +1989,8 @@ class PhotosDB:
|
||||
|
||||
info["hidden"] = row[9]
|
||||
info["favorite"] = row[10]
|
||||
info["originalFilename"] = row[3]
|
||||
info["filename"] = row[12]
|
||||
info["originalFilename"] = normalize_unicode(row[3])
|
||||
info["filename"] = normalize_unicode(row[12])
|
||||
info["directory"] = row[11]
|
||||
|
||||
# set latitude and longitude
|
||||
@@ -2075,7 +2022,7 @@ class PhotosDB:
|
||||
elif row[17] == 1:
|
||||
info["type"] = _MOVIE_TYPE
|
||||
else:
|
||||
if _debug():
|
||||
if is_debug():
|
||||
logging.debug(f"WARNING: {uuid} found unknown type {row[17]}")
|
||||
info["type"] = None
|
||||
|
||||
@@ -2274,7 +2221,7 @@ class PhotosDB:
|
||||
if uuid in self._dbphotos:
|
||||
self._dbphotos[uuid]["extendedDescription"] = normalize_unicode(row[1])
|
||||
else:
|
||||
if _debug():
|
||||
if is_debug():
|
||||
logging.debug(
|
||||
f"WARNING: found description {row[1]} but no photo for {uuid}"
|
||||
)
|
||||
@@ -2293,7 +2240,7 @@ class PhotosDB:
|
||||
if uuid in self._dbphotos:
|
||||
self._dbphotos[uuid]["adjustmentFormatID"] = row[2]
|
||||
else:
|
||||
if _debug():
|
||||
if is_debug():
|
||||
logging.debug(
|
||||
f"WARNING: found adjustmentformatidentifier {row[2]} but no photo for uuid {row[0]}"
|
||||
)
|
||||
@@ -2519,50 +2466,7 @@ class PhotosDB:
|
||||
verbose("Processing moments.")
|
||||
self._process_moments()
|
||||
|
||||
# done processing, dump debug data if requested
|
||||
verbose("Done processing details from Photos library.")
|
||||
if _debug():
|
||||
logging.debug("Faces (_dbfaces_uuid):")
|
||||
logging.debug(pformat(self._dbfaces_uuid))
|
||||
|
||||
logging.debug("Persons (_dbpersons_pk):")
|
||||
logging.debug(pformat(self._dbpersons_pk))
|
||||
|
||||
logging.debug("Keywords by uuid (_dbkeywords_uuid):")
|
||||
logging.debug(pformat(self._dbkeywords_uuid))
|
||||
|
||||
logging.debug("Keywords by keyword (_dbkeywords_keywords):")
|
||||
logging.debug(pformat(self._dbkeywords_keyword))
|
||||
|
||||
logging.debug("Albums by uuid (_dbalbums_uuid):")
|
||||
logging.debug(pformat(self._dbalbums_uuid))
|
||||
|
||||
logging.debug("Albums by album (_dbalbums_albums):")
|
||||
logging.debug(pformat(self._dbalbums_album))
|
||||
|
||||
logging.debug("Album details (_dbalbum_details):")
|
||||
logging.debug(pformat(self._dbalbum_details))
|
||||
|
||||
logging.debug("Album titles (_dbalbum_titles):")
|
||||
logging.debug(pformat(self._dbalbum_titles))
|
||||
|
||||
logging.debug("Album folders (_dbalbum_folders):")
|
||||
logging.debug(pformat(self._dbalbum_folders))
|
||||
|
||||
logging.debug("Album parent folders (_dbalbum_parent_folders):")
|
||||
logging.debug(pformat(self._dbalbum_parent_folders))
|
||||
|
||||
logging.debug("Albums pk (_dbalbums_pk):")
|
||||
logging.debug(pformat(self._dbalbums_pk))
|
||||
|
||||
logging.debug("Volumes (_dbvolumes):")
|
||||
logging.debug(pformat(self._dbvolumes))
|
||||
|
||||
logging.debug("Photos (_dbphotos):")
|
||||
logging.debug(pformat(self._dbphotos))
|
||||
|
||||
logging.debug("Burst Photos (dbphotos_burst:")
|
||||
logging.debug(pformat(self._dbphotos_burst))
|
||||
|
||||
def _process_moments(self):
|
||||
"""Process data from ZMOMENT table"""
|
||||
@@ -2623,8 +2527,8 @@ class PhotosDB:
|
||||
moment_info["modificationDate"] = row[6]
|
||||
moment_info["representativeDate"] = row[7]
|
||||
moment_info["startDate"] = row[8]
|
||||
moment_info["subtitle"] = row[9]
|
||||
moment_info["title"] = row[10]
|
||||
moment_info["subtitle"] = normalize_unicode(row[9])
|
||||
moment_info["title"] = normalize_unicode(row[10])
|
||||
moment_info["uuid"] = row[11]
|
||||
|
||||
# if both lat/lon == -180, then it means location undefined
|
||||
@@ -3027,6 +2931,7 @@ class PhotosDB:
|
||||
if keywords:
|
||||
keyword_set = set()
|
||||
for keyword in keywords:
|
||||
keyword = normalize_unicode(keyword)
|
||||
if keyword in self._dbkeywords_keyword:
|
||||
keyword_set.update(self._dbkeywords_keyword[keyword])
|
||||
photos_sets.append(keyword_set)
|
||||
@@ -3034,6 +2939,7 @@ class PhotosDB:
|
||||
if persons:
|
||||
person_set = set()
|
||||
for person in persons:
|
||||
person = normalize_unicode(person)
|
||||
if person in self._dbpersons_fullname:
|
||||
for pk in self._dbpersons_fullname[person]:
|
||||
try:
|
||||
@@ -3076,8 +2982,6 @@ class PhotosDB:
|
||||
):
|
||||
info = PhotoInfo(db=self, uuid=p, info=self._dbphotos[p])
|
||||
photoinfo.append(info)
|
||||
if _debug:
|
||||
logging.debug(f"photoinfo: {pformat(photoinfo)}")
|
||||
|
||||
return photoinfo
|
||||
|
||||
@@ -3385,27 +3289,6 @@ class PhotosDB:
|
||||
if options.to_time:
|
||||
photos = [p for p in photos if p.date.time() <= options.to_time]
|
||||
|
||||
if options.burst_photos:
|
||||
# add the burst_photos to the export set
|
||||
photos_burst = [p for p in photos if p.burst]
|
||||
for burst in photos_burst:
|
||||
if options.missing_bursts:
|
||||
# include burst photos that are missing
|
||||
photos.extend(burst.burst_photos)
|
||||
else:
|
||||
# don't include missing burst images (these can't be downloaded with AppleScript)
|
||||
photos.extend([p for p in burst.burst_photos if not p.ismissing])
|
||||
|
||||
# remove duplicates as each burst photo in the set that's selected would
|
||||
# result in the entire set being added above
|
||||
# can't use set() because PhotoInfo not hashable
|
||||
seen_uuids = {}
|
||||
for p in photos:
|
||||
if p.uuid in seen_uuids:
|
||||
continue
|
||||
seen_uuids[p.uuid] = p
|
||||
photos = list(seen_uuids.values())
|
||||
|
||||
if name:
|
||||
# search filename fields for text
|
||||
# if more than one, find photos with all title values in filename
|
||||
@@ -3414,23 +3297,35 @@ class PhotosDB:
|
||||
# case-insensitive
|
||||
for n in name:
|
||||
n = n.lower()
|
||||
photo_list.extend(
|
||||
[
|
||||
p
|
||||
for p in photos
|
||||
if n in p.filename.lower()
|
||||
or n in p.original_filename.lower()
|
||||
]
|
||||
)
|
||||
if self._db_version >= _PHOTOS_5_VERSION:
|
||||
# search only original_filename (#594)
|
||||
photo_list.extend(
|
||||
[p for p in photos if n in p.original_filename.lower()]
|
||||
)
|
||||
else:
|
||||
photo_list.extend(
|
||||
[
|
||||
p
|
||||
for p in photos
|
||||
if n in p.filename.lower()
|
||||
or n in p.original_filename.lower()
|
||||
]
|
||||
)
|
||||
else:
|
||||
for n in name:
|
||||
photo_list.extend(
|
||||
[
|
||||
p
|
||||
for p in photos
|
||||
if n in p.filename or n in p.original_filename
|
||||
]
|
||||
)
|
||||
if self._db_version >= _PHOTOS_5_VERSION:
|
||||
# search only original_filename (#594)
|
||||
photo_list.extend(
|
||||
[p for p in photos if n in p.original_filename]
|
||||
)
|
||||
else:
|
||||
photo_list.extend(
|
||||
[
|
||||
p
|
||||
for p in photos
|
||||
if n in p.filename or n in p.original_filename
|
||||
]
|
||||
)
|
||||
photos = photo_list
|
||||
|
||||
if options.min_size:
|
||||
@@ -3544,6 +3439,28 @@ class PhotosDB:
|
||||
for function in options.function:
|
||||
photos = function[0](photos)
|
||||
|
||||
# burst should be checked last, ref #640
|
||||
if options.burst_photos:
|
||||
# add the burst_photos to the export set
|
||||
photos_burst = [p for p in photos if p.burst]
|
||||
for burst in photos_burst:
|
||||
if options.missing_bursts:
|
||||
# include burst photos that are missing
|
||||
photos.extend(burst.burst_photos)
|
||||
else:
|
||||
# don't include missing burst images (these can't be downloaded with AppleScript)
|
||||
photos.extend([p for p in burst.burst_photos if not p.ismissing])
|
||||
|
||||
# remove duplicates as each burst photo in the set that's selected would
|
||||
# result in the entire set being added above
|
||||
# can't use set() because PhotoInfo not hashable
|
||||
seen_uuids = {}
|
||||
for p in photos:
|
||||
if p.uuid in seen_uuids:
|
||||
continue
|
||||
seen_uuids[p.uuid] = p
|
||||
photos = list(seen_uuids.values())
|
||||
|
||||
return photos
|
||||
|
||||
def execute(self, sql):
|
||||
|
||||
@@ -99,7 +99,7 @@ OPERATOR:
|
||||
PathSep:
|
||||
(
|
||||
"("
|
||||
(value=/[^\(\)\{\}]{0,1}/)?
|
||||
(value=/[^\(\)\{\}]+/)?
|
||||
")"
|
||||
)?
|
||||
;
|
||||
|
||||
24
osxphotos/rich_utils.py
Normal file
24
osxphotos/rich_utils.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""utilities for working with rich markup"""
|
||||
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def add_rich_markup_tag(tag: str, rich=True) -> Callable:
|
||||
"""Returns function that rich markup tags to string"""
|
||||
|
||||
if not rich:
|
||||
return no_markup
|
||||
|
||||
def add_tag(msg: str) -> str:
|
||||
"""Add tag to string"""
|
||||
return f"[{tag}]{msg}[/{tag}]"
|
||||
|
||||
return add_tag
|
||||
|
||||
|
||||
def no_markup(msg: str) -> str:
|
||||
"""Return msg without markup"""
|
||||
return msg
|
||||
|
||||
|
||||
__all__ = ["add_rich_markup_tag", "no_markup"]
|
||||
@@ -211,10 +211,12 @@ class SearchInfo:
|
||||
"""return list of text for a specified category ID"""
|
||||
if self._db_searchinfo:
|
||||
content = "normalized_string" if self._normalized else "content_string"
|
||||
return [
|
||||
rec[content]
|
||||
for rec in self._db_searchinfo
|
||||
if rec["category"] == category
|
||||
]
|
||||
return sorted(
|
||||
[
|
||||
rec[content]
|
||||
for rec in self._db_searchinfo
|
||||
if rec["category"] == category
|
||||
]
|
||||
)
|
||||
else:
|
||||
return []
|
||||
|
||||
@@ -29,7 +29,7 @@ def sqlgrep(
|
||||
flags = re.IGNORECASE if ignore_case else 0
|
||||
try:
|
||||
with sqlite3.connect(f"file:{filename}?mode=ro", uri=True) as conn:
|
||||
regex = re.compile(r"(" + pattern + r")", flags=flags)
|
||||
regex = re.compile(f'({pattern})', flags=flags)
|
||||
filename_header = f"{filename}: " if print_filename else ""
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
@@ -54,4 +54,4 @@ def sqlgrep(
|
||||
field_value,
|
||||
]
|
||||
except sqlite3.DatabaseError as e:
|
||||
raise sqlite3.DatabaseError(f"{filename}: {e}")
|
||||
raise sqlite3.DatabaseError(f"{filename}: {e}") from e
|
||||
|
||||
@@ -103,6 +103,8 @@
|
||||
% if photo.face_info:
|
||||
<mwg-rs:Regions rdf:parseType="Resource">
|
||||
<mwg-rs:AppliedToDimensions rdf:parseType="Resource">
|
||||
<stDim:h>${photo.width if photo.orientation in [5, 6, 7, 8] else photo.height}</stDim:h>
|
||||
<stDim:w>${photo.height if photo.orientation in [5, 6, 7, 8] else photo.width}</stDim:w>
|
||||
<stDim:unit>pixel</stDim:unit>
|
||||
</mwg-rs:AppliedToDimensions>
|
||||
<mwg-rs:RegionList>
|
||||
|
||||
@@ -103,6 +103,8 @@
|
||||
% if photo.face_info:
|
||||
<mwg-rs:Regions rdf:parseType="Resource">
|
||||
<mwg-rs:AppliedToDimensions rdf:parseType="Resource">
|
||||
<stDim:h>${photo.width if photo.orientation in [5, 6, 7, 8] else photo.height}</stDim:h>
|
||||
<stDim:w>${photo.height if photo.orientation in [5, 6, 7, 8] else photo.width}</stDim:w>
|
||||
<stDim:unit>pixel</stDim:unit>
|
||||
</mwg-rs:AppliedToDimensions>
|
||||
<mwg-rs:RegionList>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
""" Utility functions used in osxphotos """
|
||||
|
||||
import datetime
|
||||
import fnmatch
|
||||
import glob
|
||||
import importlib
|
||||
@@ -16,18 +17,15 @@ import sys
|
||||
import unicodedata
|
||||
import urllib.parse
|
||||
from plistlib import load as plistload
|
||||
from typing import Callable, List, Union
|
||||
from typing import Callable, List, Union, Optional
|
||||
|
||||
import CoreFoundation
|
||||
import objc
|
||||
from Foundation import NSFileManager, NSPredicate, NSString
|
||||
|
||||
from ._constants import UNICODE_FORMAT
|
||||
|
||||
__all__ = [
|
||||
"dd_to_dms_str",
|
||||
"expand_and_validate_filepath",
|
||||
"findfiles",
|
||||
"get_last_library_path",
|
||||
"get_system_library_path",
|
||||
"increment_filename_with_count",
|
||||
@@ -41,17 +39,12 @@ __all__ = [
|
||||
"normalize_unicode",
|
||||
]
|
||||
|
||||
_DEBUG = False
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s - %(levelname)s - %(filename)s - %(lineno)d - %(message)s",
|
||||
)
|
||||
|
||||
if not _DEBUG:
|
||||
logging.disable(logging.DEBUG)
|
||||
|
||||
|
||||
def _get_logger():
|
||||
"""Used only for testing
|
||||
@@ -62,21 +55,6 @@ def _get_logger():
|
||||
return logging.Logger(__name__)
|
||||
|
||||
|
||||
def _set_debug(debug):
|
||||
"""Enable or disable debug logging"""
|
||||
global _DEBUG
|
||||
_DEBUG = debug
|
||||
if debug:
|
||||
logging.disable(logging.NOTSET)
|
||||
else:
|
||||
logging.disable(logging.DEBUG)
|
||||
|
||||
|
||||
def _debug():
|
||||
"""returns True if debugging turned on (via _set_debug), otherwise, false"""
|
||||
return _DEBUG
|
||||
|
||||
|
||||
def noop(*args, **kwargs):
|
||||
"""do nothing (no operation)"""
|
||||
pass
|
||||
@@ -265,11 +243,12 @@ def list_photo_libraries():
|
||||
# On older MacOS versions, mdfind appears to ignore some libraries
|
||||
# glob to find libraries in ~/Pictures then mdfind to find all the others
|
||||
# TODO: make this more robust
|
||||
lib_list = glob.glob(f"{pathlib.Path.home()}/Pictures/*.photoslibrary")
|
||||
lib_list = list_directory(
|
||||
f"{pathlib.Path.home()}/Pictures/", glob="*.photoslibrary"
|
||||
)
|
||||
|
||||
# On older OS, may not get all libraries so make sure we get the last one
|
||||
last_lib = get_last_library_path()
|
||||
if last_lib:
|
||||
if last_lib := get_last_library_path():
|
||||
lib_list.append(last_lib)
|
||||
|
||||
output = subprocess.check_output(
|
||||
@@ -277,8 +256,7 @@ def list_photo_libraries():
|
||||
).splitlines()
|
||||
for lib in output:
|
||||
lib_list.append(lib.decode("utf-8"))
|
||||
lib_list = list(set(lib_list))
|
||||
lib_list.sort()
|
||||
lib_list = sorted(set(lib_list))
|
||||
return lib_list
|
||||
|
||||
|
||||
@@ -289,27 +267,90 @@ def normalize_fs_path(path: str) -> str:
|
||||
return unicodedata.normalize("NFD", path)
|
||||
|
||||
|
||||
def findfiles(pattern, path):
|
||||
"""Returns list of filenames from path matched by pattern
|
||||
shell pattern. Matching is case-insensitive.
|
||||
If 'path_' is invalid/doesn't exist, returns []."""
|
||||
if not os.path.isdir(path):
|
||||
# def findfiles(pattern, path):
|
||||
# """Returns list of filenames from path matched by pattern
|
||||
# shell pattern. Matching is case-insensitive.
|
||||
# If 'path_' is invalid/doesn't exist, returns []."""
|
||||
# if not os.path.isdir(path):
|
||||
# return []
|
||||
|
||||
# # paths need to be normalized for unicode as filesystem returns unicode in NFD form
|
||||
# pattern = normalize_fs_path(pattern)
|
||||
# rule = re.compile(fnmatch.translate(pattern), re.IGNORECASE)
|
||||
# files = os.listdir(path)
|
||||
# return [name for name in files if rule.match(name)]
|
||||
|
||||
|
||||
def list_directory(
|
||||
directory: Union[str, pathlib.Path],
|
||||
startswith: Optional[str] = None,
|
||||
endswith: Optional[str] = None,
|
||||
contains: Optional[str] = None,
|
||||
glob: Optional[str] = None,
|
||||
include_path: bool = False,
|
||||
case_sensitive: bool = False,
|
||||
) -> List[Union[str, pathlib.Path]]:
|
||||
"""List directory contents and return list of files or directories matching search criteria.
|
||||
Accounts for case-insensitive filesystems, unicode filenames. directory can be a str or a pathlib.Path object.
|
||||
|
||||
Args:
|
||||
directory: directory to search
|
||||
startswith: string to match at start of filename
|
||||
endswith: string to match at end of filename
|
||||
contains: string to match anywhere in filename
|
||||
glob: shell-style glob pattern to match filename
|
||||
include_path: if True, return full path to file
|
||||
case_sensitive: if True, match case-sensitively
|
||||
|
||||
Returns: List of files or directories matching search criteria as either str or pathlib.Path objects depending on the input type;
|
||||
returns empty list if directory is invalid or doesn't exist.
|
||||
|
||||
"""
|
||||
is_pathlib = isinstance(directory, pathlib.Path)
|
||||
if is_pathlib:
|
||||
directory = str(directory)
|
||||
|
||||
if not os.path.isdir(directory):
|
||||
return []
|
||||
|
||||
# paths need to be normalized for unicode as filesystem returns unicode in NFD form
|
||||
pattern = normalize_fs_path(pattern)
|
||||
rule = re.compile(fnmatch.translate(pattern), re.IGNORECASE)
|
||||
files = os.listdir(path)
|
||||
return [name for name in files if rule.match(name)]
|
||||
startswith = normalize_fs_path(startswith) if startswith else None
|
||||
endswith = normalize_fs_path(endswith) if endswith else None
|
||||
contains = normalize_fs_path(contains) if contains else None
|
||||
glob = normalize_fs_path(glob) if glob else None
|
||||
|
||||
files = [normalize_fs_path(f) for f in os.listdir(directory)]
|
||||
if not case_sensitive:
|
||||
files_normalized = {f.lower(): f for f in files}
|
||||
files = [f.lower() for f in files]
|
||||
startswith = startswith.lower() if startswith else None
|
||||
endswith = endswith.lower() if endswith else None
|
||||
contains = contains.lower() if contains else None
|
||||
glob = glob.lower() if glob else None
|
||||
else:
|
||||
files_normalized = {f: f for f in files}
|
||||
|
||||
def list_directory_startswith(directory_path: str, startswith: str) -> List[str]:
|
||||
"""List directory contents and return list of files starting with startswith; returns [] if directory doesn't exist"""
|
||||
if not os.path.isdir(directory_path):
|
||||
return []
|
||||
startswith = normalize_fs_path(startswith)
|
||||
files = [normalize_fs_path(f) for f in os.listdir(directory_path)]
|
||||
return [f for f in files if f.startswith(startswith)]
|
||||
if startswith:
|
||||
files = [f for f in files if f.startswith(startswith)]
|
||||
if endswith:
|
||||
endswith = normalize_fs_path(endswith)
|
||||
files = [f for f in files if f.endswith(endswith)]
|
||||
if contains:
|
||||
contains = normalize_fs_path(contains)
|
||||
files = [f for f in files if contains in f]
|
||||
if glob:
|
||||
glob = normalize_fs_path(glob)
|
||||
flags = re.IGNORECASE if not case_sensitive else 0
|
||||
rule = re.compile(fnmatch.translate(glob), flags)
|
||||
files = [f for f in files if rule.match(f)]
|
||||
|
||||
files = [files_normalized[f] for f in files]
|
||||
|
||||
if include_path:
|
||||
files = [os.path.join(directory, f) for f in files]
|
||||
if is_pathlib:
|
||||
files = [pathlib.Path(f) for f in files]
|
||||
|
||||
return files
|
||||
|
||||
|
||||
def _open_sql_file(dbname):
|
||||
@@ -380,8 +421,8 @@ def increment_filename_with_count(
|
||||
Note: This obviously is subject to race condition so using with caution.
|
||||
"""
|
||||
dest = filepath if isinstance(filepath, pathlib.Path) else pathlib.Path(filepath)
|
||||
dest_files = list_directory_startswith(str(dest.parent), dest.stem)
|
||||
dest_files = [pathlib.Path(f).stem.lower() for f in dest_files]
|
||||
dest_files = list_directory(dest.parent, startswith=dest.stem)
|
||||
dest_files = [f.stem.lower() for f in dest_files]
|
||||
dest_new = f"{dest.stem} ({count})" if count else dest.stem
|
||||
dest_new = normalize_fs_path(dest_new)
|
||||
|
||||
@@ -440,10 +481,19 @@ def load_function(pyfile: str, function_name: str) -> Callable:
|
||||
|
||||
try:
|
||||
func = getattr(module, function_name)
|
||||
except AttributeError:
|
||||
raise ValueError(f"'{function_name}' not found in module '{module_name}'")
|
||||
except AttributeError as e:
|
||||
raise ValueError(
|
||||
f"'{function_name}' not found in module '{module_name}'"
|
||||
) from e
|
||||
|
||||
finally:
|
||||
# restore sys.path
|
||||
sys.path = syspath
|
||||
|
||||
return func
|
||||
|
||||
|
||||
def format_sec_to_hhmmss(sec: float) -> str:
|
||||
"""Format seconds to hh:mm:ss"""
|
||||
delta = datetime.timedelta(seconds=sec)
|
||||
return str(delta).split(".")[0]
|
||||
|
||||
2
pytest.ini
Normal file
2
pytest.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
addopts = -p tests.plugins.env_vars
|
||||
@@ -1,26 +1,26 @@
|
||||
Click>=8.0.1,<9.0
|
||||
Mako>=1.1.4,<1.2.0
|
||||
PyYAML>=5.4.1,<6.0.0
|
||||
bitmath>=1.3.3.1,<1.4.0.0
|
||||
bpylist2==3.0.2
|
||||
dataclasses==0.7;python_version<'3.7'
|
||||
Click>=8.0.4,<9.0
|
||||
Mako>=1.1.4,<1.2.0
|
||||
more-itertools>=8.8.0,<9.0.0
|
||||
objexplore>=1.5.5,<1.6.0
|
||||
objexplore>=1.6.3,<2.0.0
|
||||
osxmetadata>=0.99.34,<1.0.0
|
||||
pathvalidate>=2.4.1,<2.5.0
|
||||
photoscript>=0.1.4,<0.2.0
|
||||
ptpython>=3.0.20,<3.1.0
|
||||
pyobjc-core>=7.3,<9.0
|
||||
pyobjc-framework-AVFoundation>=7.3,<9.0
|
||||
pyobjc-framework-AppleScriptKit>=7.3,<9.0
|
||||
pyobjc-framework-AppleScriptObjC>=7.3,<9.0
|
||||
pyobjc-framework-AVFoundation>=7.3,<9.0
|
||||
pyobjc-framework-Cocoa>=7.3,<9.0
|
||||
pyobjc-framework-CoreServices>=7.2,<9.0
|
||||
pyobjc-framework-Metal>=7.3,<9.0
|
||||
pyobjc-framework-Photos>=7.3,<9.0
|
||||
pyobjc-framework-Quartz>=7.3,<9.0
|
||||
pyobjc-framework-Vision>=7.3,<9.0
|
||||
rich>=10.6.0,<=11.0.0
|
||||
PyYAML>=5.4.1,<6.0.0
|
||||
rich>=11.2.0,<12.0.0
|
||||
textx>=2.3.0,<2.4.0
|
||||
toml>=0.10.2,<0.11.0
|
||||
wrapt>=1.13.3,<1.14.0
|
||||
wurlitzer>=2.1.0,<2.2.0
|
||||
11
setup.py
11
setup.py
@@ -67,21 +67,19 @@ setup(
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
],
|
||||
install_requires=[
|
||||
"Click>=8.0.1,<9.0",
|
||||
"Click>=8.0.4,<9.0",
|
||||
"Mako>=1.1.4,<1.2.0",
|
||||
"PyYAML>=5.4.1,<5.5.0",
|
||||
"bitmath>=1.3.3.1,<1.4.0.0",
|
||||
"bpylist2==3.0.2",
|
||||
"dataclasses==0.7;python_version<'3.7'",
|
||||
"more-itertools>=8.8.0,<9.0.0",
|
||||
"objexplore>=1.5.5,<1.6.0",
|
||||
"objexplore>=1.6.3,<2.0.0",
|
||||
"osxmetadata>=0.99.34,<1.0.0",
|
||||
"pathvalidate>=2.4.1,<3.0.0",
|
||||
"photoscript>=0.1.4,<0.2.0",
|
||||
@@ -96,11 +94,12 @@ setup(
|
||||
"pyobjc-framework-Photos>=7.3,<9.0",
|
||||
"pyobjc-framework-Quartz>=7.3,<9.0",
|
||||
"pyobjc-framework-Vision>=7.3,<9.0",
|
||||
"rich>=10.6.0,<=11.0.0",
|
||||
"rich>=11.2.0,<12.0.0",
|
||||
"textx>=2.3.0,<3.0.0",
|
||||
"toml>=0.10.2,<0.11.0",
|
||||
"wrapt>=1.13.3,<1.14.0",
|
||||
"wurlitzer>=2.1.0,<3.0.0",
|
||||
],
|
||||
entry_points={"console_scripts": ["osxphotos=osxphotos.__main__:cli"]},
|
||||
entry_points={"console_scripts": ["osxphotos=osxphotos.__main__:cli_main"]},
|
||||
include_package_data=True,
|
||||
)
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
# Tests for osxphotos #
|
||||
|
||||
## Running Tests ##
|
||||
Tests require pytest and pytest-mock:
|
||||
`pip install pytest`
|
||||
`pip install pytest-mock`
|
||||
To set up a dev environment to work on osxphotos code or run tests follow these steps. This assumes you have python 3.7 or later installed. If you need to install python, you can do so with the XCode command lines tools (`xcode-select --install`) or from [python.org](https://www.python.org/downloads/macos/).
|
||||
|
||||
- `git clone git@github.com:RhetTbull/osxphotos.git`
|
||||
- `cd osxphotos`
|
||||
- `python3 -m venv venv`
|
||||
- `source venv/bin/activate`
|
||||
- `python3 -m pip install -r dev_requirements.txt`
|
||||
- `python3 -m pip install -e .`
|
||||
|
||||
To run the tests, do the following from the main source folder:
|
||||
`python -m pytest tests/`
|
||||
`python3 -m pytest tests/`
|
||||
|
||||
Running the tests this way allows the library to be tested without installing it.
|
||||
|
||||
## Skipped Tests ##
|
||||
A few tests will look for certain environment variables to determine if they should run.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,7 +7,7 @@
|
||||
<key>hostuuid</key>
|
||||
<string>585B80BF-8D1F-55EF-A9E8-6CF4E5523959</string>
|
||||
<key>pid</key>
|
||||
<integer>1961</integer>
|
||||
<integer>14817</integer>
|
||||
<key>processname</key>
|
||||
<string>photolibraryd</string>
|
||||
<key>uid</key>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user