Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
202bc1144b | ||
|
|
a0c654e43f | ||
|
|
2bb677dc19 | ||
|
|
e33805fe42 | ||
|
|
04ac0a1121 | ||
|
|
d2b0bd4e28 | ||
|
|
d754899563 | ||
|
|
4a81e643a7 | ||
|
|
b23e74f8f5 | ||
|
|
5dc766249a | ||
|
|
a895833c7f | ||
|
|
3f81a3c179 | ||
|
|
f1235f745f | ||
|
|
1ddb1de998 | ||
|
|
c472698b1d | ||
|
|
4e021a0731 | ||
|
|
bfbc156821 | ||
|
|
bfd6274602 | ||
|
|
3abaa5ae84 | ||
|
|
65115a50a9 | ||
|
|
06138e15d0 | ||
|
|
14710e3178 | ||
|
|
f705f09749 | ||
|
|
82c445f41e | ||
|
|
1b40e9d65f | ||
|
|
725f7c8735 | ||
|
|
7cc8578148 | ||
|
|
6adafb8ce7 | ||
|
|
ac47df8475 | ||
|
|
f680cf78ab | ||
|
|
c86e84c534 | ||
|
|
3fb611825c | ||
|
|
1cfdad0176 | ||
|
|
59ba325273 | ||
|
|
c4b7c2623f | ||
|
|
e5b2d2ee45 |
@@ -250,6 +250,15 @@
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "oPromessa",
|
||||
"name": "oPromessa",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/21261491?v=4",
|
||||
"profile": "https://github.com/oPromessa",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
57
CHANGELOG.md
57
CHANGELOG.md
@@ -4,6 +4,63 @@ 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.42.91](https://github.com/RhetTbull/osxphotos/compare/v0.42.90...v0.42.91)
|
||||
|
||||
> 11 October 2021
|
||||
|
||||
- Updated docs [skip ci] [`b23e74f`](https://github.com/RhetTbull/osxphotos/commit/b23e74f8f5a8387564108c330c3f8ac11189860d)
|
||||
- Added python 3.10 to supported versions [`3f81a3c`](https://github.com/RhetTbull/osxphotos/commit/3f81a3c179dde37e9811ef19c847920bb3bd514c)
|
||||
- Updated dependencies [`a895833`](https://github.com/RhetTbull/osxphotos/commit/a895833c7f0a264488e671f1735f9e10d2618e2d)
|
||||
|
||||
#### [v0.42.90](https://github.com/RhetTbull/osxphotos/compare/v0.42.89...v0.42.90)
|
||||
|
||||
> 30 September 2021
|
||||
|
||||
- Updated REPL, now with more cowbell [`c472698`](https://github.com/RhetTbull/osxphotos/commit/c472698b1d0d8ff9f4d1bde715859bf766f99290)
|
||||
- Updated docs [skip ci] [`1ddb1de`](https://github.com/RhetTbull/osxphotos/commit/1ddb1de99841e65b690ffc1cbcc5e42e6e25f727)
|
||||
|
||||
#### [v0.42.89](https://github.com/RhetTbull/osxphotos/compare/v0.42.88...v0.42.89)
|
||||
|
||||
> 26 September 2021
|
||||
|
||||
- Updated docs [skip ci] [`bfbc156`](https://github.com/RhetTbull/osxphotos/commit/bfbc156821d2d262b7bd9c4437e23e310da10769)
|
||||
- Updated docs [skip ci] [`3abaa5a`](https://github.com/RhetTbull/osxphotos/commit/3abaa5ae84ca44cd900f1e3af4532ab405d41a09)
|
||||
- Fixed AlbumInfo.owner, #239 [`bfd6274`](https://github.com/RhetTbull/osxphotos/commit/bfd627460255c65f870bca6d036401e8792d29d5)
|
||||
|
||||
#### [v0.42.88](https://github.com/RhetTbull/osxphotos/compare/v0.42.87...v0.42.88)
|
||||
|
||||
> 26 September 2021
|
||||
|
||||
- Performance fix for #239, owner [`14710e3`](https://github.com/RhetTbull/osxphotos/commit/14710e31789d71b2c948a37722fb6054aca4d85e)
|
||||
- version bump [`06138e1`](https://github.com/RhetTbull/osxphotos/commit/06138e15d0b87e4865a9ef0cc542303edb44c861)
|
||||
|
||||
#### [v0.42.87](https://github.com/RhetTbull/osxphotos/compare/v0.42.86...v0.42.87)
|
||||
|
||||
> 26 September 2021
|
||||
|
||||
#### [v0.42.86](https://github.com/RhetTbull/osxphotos/compare/v0.42.85...v0.42.86)
|
||||
|
||||
> 26 September 2021
|
||||
|
||||
- Fix for #517, #239 [`ac47df8`](https://github.com/RhetTbull/osxphotos/commit/ac47df8475762fe8c8f63ad5ffa83b1e20d116b8)
|
||||
- Fixed formatting [`6adafb8`](https://github.com/RhetTbull/osxphotos/commit/6adafb8ce70e95a9f0bec1a3db6362742fcd1b0d)
|
||||
- Updated docs [skip ci] [`725f7c8`](https://github.com/RhetTbull/osxphotos/commit/725f7c87351353efeee8c43c3c7f8a95acb14490)
|
||||
|
||||
#### [v0.42.85](https://github.com/RhetTbull/osxphotos/compare/v0.42.84...v0.42.85)
|
||||
|
||||
> 25 September 2021
|
||||
|
||||
- Implemented PhotoInfo.owner, AlbumInfo.owner, #216, #239 [`c4b7c26`](https://github.com/RhetTbull/osxphotos/commit/c4b7c2623f077d9964d5d578ce6c01bb83fab088)
|
||||
- Updated docs [skip ci] [`59ba325`](https://github.com/RhetTbull/osxphotos/commit/59ba325273b2f16935be944fd46c1237ce637bb8)
|
||||
|
||||
#### [v0.42.84](https://github.com/RhetTbull/osxphotos/compare/v0.42.83...v0.42.84)
|
||||
|
||||
> 25 September 2021
|
||||
|
||||
- Fix for #516 [`e3e1da2`](https://github.com/RhetTbull/osxphotos/commit/e3e1da2fd898896595fc851288f905bd4e2150f8)
|
||||
- Updated docs [skip ci] [`64c226b`](https://github.com/RhetTbull/osxphotos/commit/64c226b85529581e393a2d0604b41c37a8dc2eaf)
|
||||
- Update docs [`c429a86`](https://github.com/RhetTbull/osxphotos/commit/c429a860b1ebeb77f3c3e36e9660fc9153d85d11)
|
||||
|
||||
#### [v0.42.83](https://github.com/RhetTbull/osxphotos/compare/v0.42.82...v0.42.83)
|
||||
|
||||
> 15 September 2021
|
||||
|
||||
@@ -2,4 +2,5 @@ include README.md
|
||||
include README.rst
|
||||
include osxphotos/templates/*
|
||||
include osxphotos/phototemplate.tx
|
||||
include osxphotos/phototemplate.md
|
||||
include osxphotos/phototemplate.md
|
||||
include osxphotos/queries/*
|
||||
25
README.md
25
README.md
@@ -4,7 +4,9 @@
|
||||
[](https://github.com/RhetTbull/osxphotos/workflows/Tests/badge.svg)
|
||||

|
||||
[](https://pepy.tech/project/osxphotos)
|
||||
[](#contributors)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
OSXPhotos provides the ability to interact with and query Apple's Photos.app library on macOS. You can query the Photos library database — for example, file name, file path, and metadata such as keywords/tags, persons/faces, albums, etc. You can also easily export both the original and edited photos.
|
||||
|
||||
@@ -1702,7 +1704,7 @@ Substitution Description
|
||||
{lf} A line feed: '\n', alias for {newline}
|
||||
{cr} A carriage return: '\r'
|
||||
{crlf} a carriage return + line feed: '\r\n'
|
||||
{osxphotos_version} The osxphotos version, e.g. '0.42.84'
|
||||
{osxphotos_version} The osxphotos version, e.g. '0.42.93'
|
||||
{osxphotos_cmd_line} The full command line used to run osxphotos
|
||||
|
||||
The following substitutions may result in multiple values. Thus if specified for
|
||||
@@ -2373,6 +2375,8 @@ For example, in my library, Photos says I have 19,386 photos and 474 movies. Ho
|
||||
#### <a name="getphoto">`get_photo(uuid)`</A>
|
||||
Returns a single PhotoInfo instance for photo with UUID matching `uuid` or None if no photo is found matching `uuid`. If you know the UUID of a photo, `get_photo()` is much faster than `photos`. See also [photos()](#photos).
|
||||
|
||||
#### `execute(sql)`
|
||||
Execute sql statement against the Photos database and return a sqlite cursor with the results.
|
||||
|
||||
### PhotoInfo
|
||||
PhotosDB.photos() returns a list of PhotoInfo objects. Each PhotoInfo object represents a single photo in the Photos library.
|
||||
@@ -2516,7 +2520,12 @@ Returns a [PlaceInfo](#PlaceInfo) object with reverse geolocation data or None i
|
||||
#### `shared`
|
||||
Returns True if photo is in a shared album, otherwise False.
|
||||
|
||||
**Note**: *Only valid on Photos 5 / MacOS 10.15+; on Photos <= 4, returns None instead of True/False.
|
||||
**Note**: *Only valid on Photos 5 / MacOS 10.15+; on Photos <= 4, returns None.
|
||||
|
||||
#### `owner`
|
||||
Returns full name of the photo owner (person who shared the photo) for shared photos or None if photo is not shared. Also returns None if you are the person who shared the photo.
|
||||
|
||||
**Note**: *Only valid on Photos 5 / MacOS 10.15+; on Photos <= 4, returns None.
|
||||
|
||||
#### `comments`
|
||||
Returns list of [CommentInfo](#commentinfo) objects for comments on shared photos or empty list if no comments.
|
||||
@@ -2890,6 +2899,11 @@ Photos Library
|
||||
#### `parent`
|
||||
Returns a [FolderInfo](#FolderInfo) object representing the albums parent folder or `None` if album is not a in a folder.
|
||||
|
||||
#### `owner`
|
||||
Returns full name of the album owner (person who shared the album) for shared albums or None if album is not shared.
|
||||
|
||||
**Note**: *Only valid on Photos 5 / MacOS 10.15+; on Photos <= 4, returns None.
|
||||
|
||||
### ImportInfo
|
||||
PhotosDB.import_info returns a list of ImportInfo objects. Each ImportInfo object represents an import session in the library. PhotoInfo.import_info returns a single ImportInfo object representing the import session for the photo (or `None` if no associated import session).
|
||||
|
||||
@@ -3561,7 +3575,7 @@ The following template field substitutions are availabe for use the templating s
|
||||
|{lf}|A line feed: '\n', alias for {newline}|
|
||||
|{cr}|A carriage return: '\r'|
|
||||
|{crlf}|a carriage return + line feed: '\r\n'|
|
||||
|{osxphotos_version}|The osxphotos version, e.g. '0.42.84'|
|
||||
|{osxphotos_version}|The osxphotos version, e.g. '0.42.93'|
|
||||
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|
||||
|{album}|Album(s) photo is contained in|
|
||||
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|
|
||||
@@ -3785,6 +3799,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>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -3826,6 +3841,8 @@ For additional details about how osxphotos is implemented or if you would like t
|
||||
- [textx](https://github.com/textX/textX)
|
||||
- [bitmath](https://github.com/tbielawa/bitmath)
|
||||
- [more-itertools](https://github.com/more-itertools/more-itertools)
|
||||
- [ptpython](https://github.com/prompt-toolkit/ptpython)
|
||||
- [objexplore](https://github.com/kylepollina/objexplore)
|
||||
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: c8c78e92de35f8ff4cf0b9d6bd35d796
|
||||
config: 642fb2b37def3cc1207bd4b6657f9fd0
|
||||
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.42.84 documentation</title>
|
||||
<title>Overview: module code — osxphotos 0.42.93 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>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos.photoinfo._photoinfo_export — osxphotos 0.42.84 documentation</title>
|
||||
<title>osxphotos.photoinfo._photoinfo_export — osxphotos 0.42.93 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../../../_static/alabaster.css" />
|
||||
<script data-url_root="../../../" id="documentation_options" src="../../../_static/documentation_options.js"></script>
|
||||
@@ -945,7 +945,11 @@
|
||||
<span class="n">preview_path</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">path_derivatives</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
|
||||
<span class="n">preview_ext</span> <span class="o">=</span> <span class="n">preview_path</span><span class="o">.</span><span class="n">suffix</span>
|
||||
<span class="n">preview_name</span> <span class="o">=</span> <span class="n">dest</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">dest</span><span class="o">.</span><span class="n">stem</span><span class="si">}{</span><span class="n">preview_suffix</span><span class="si">}{</span><span class="n">preview_ext</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="n">preview_name</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">increment_filename</span><span class="p">(</span><span class="n">preview_name</span><span class="p">))</span>
|
||||
<span class="n">preview_name</span> <span class="o">=</span> <span class="p">(</span>
|
||||
<span class="n">preview_name</span>
|
||||
<span class="k">if</span> <span class="n">overwrite</span>
|
||||
<span class="k">else</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">increment_filename</span><span class="p">(</span><span class="n">preview_name</span><span class="p">))</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">preview_path</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">results</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_export_photo</span><span class="p">(</span>
|
||||
<span class="n">preview_path</span><span class="p">,</span>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos.photoinfo.photoinfo — osxphotos 0.42.83 documentation</title>
|
||||
<title>osxphotos.photoinfo.photoinfo — osxphotos 0.42.87 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../../../_static/alabaster.css" />
|
||||
<script data-url_root="../../../" id="documentation_options" src="../../../_static/documentation_options.js"></script>
|
||||
@@ -71,6 +71,7 @@
|
||||
<span class="kn">from</span> <span class="nn">..personinfo</span> <span class="kn">import</span> <span class="n">FaceInfo</span><span class="p">,</span> <span class="n">PersonInfo</span>
|
||||
<span class="kn">from</span> <span class="nn">..phototemplate</span> <span class="kn">import</span> <span class="n">PhotoTemplate</span><span class="p">,</span> <span class="n">RenderOptions</span>
|
||||
<span class="kn">from</span> <span class="nn">..placeinfo</span> <span class="kn">import</span> <span class="n">PlaceInfo4</span><span class="p">,</span> <span class="n">PlaceInfo5</span>
|
||||
<span class="kn">from</span> <span class="nn">..query_builder</span> <span class="kn">import</span> <span class="n">get_query</span>
|
||||
<span class="kn">from</span> <span class="nn">..text_detection</span> <span class="kn">import</span> <span class="n">detect_text</span>
|
||||
<span class="kn">from</span> <span class="nn">..uti</span> <span class="kn">import</span> <span class="n">get_preferred_uti_extension</span><span class="p">,</span> <span class="n">get_uti_for_extension</span>
|
||||
<span class="kn">from</span> <span class="nn">..utils</span> <span class="kn">import</span> <span class="n">_debug</span><span class="p">,</span> <span class="n">_get_resource_loc</span><span class="p">,</span> <span class="n">findfiles</span>
|
||||
@@ -1131,6 +1132,26 @@
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Did not find signature for </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="si">}</span><span class="s2"> in _db_signatures"</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">duplicates</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">owner</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Return name of photo owner for shared photos (Photos 5+ only), or None if not shared"""</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_version</span> <span class="o"><=</span> <span class="n">_PHOTOS_4_VERSION</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="kc">None</span>
|
||||
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_owner</span>
|
||||
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">personid</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">"cloudownerhashedpersonid"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_owner</span> <span class="o">=</span> <span class="p">(</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_hashed_person_id</span><span class="p">[</span><span class="n">personid</span><span class="p">][</span><span class="s2">"full_name"</span><span class="p">]</span>
|
||||
<span class="k">if</span> <span class="n">personid</span>
|
||||
<span class="k">else</span> <span class="kc">None</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_owner</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_owner</span>
|
||||
|
||||
<div class="viewcode-block" id="PhotoInfo.render_template"><a class="viewcode-back" href="../../../reference.html#osxphotos.PhotoInfo.render_template">[docs]</a> <span class="k">def</span> <span class="nf">render_template</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span> <span class="n">template_str</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">RenderOptions</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="p">):</span>
|
||||
|
||||
@@ -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.42.80 documentation</title>
|
||||
<title>osxphotos.photosdb.photosdb — osxphotos 0.42.92 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../../../_static/alabaster.css" />
|
||||
<script data-url_root="../../../" id="documentation_options" src="../../../_static/documentation_options.js"></script>
|
||||
@@ -363,6 +363,8 @@
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_process_database5</span><span class="p">()</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_db_connection</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_db_connection</span><span class="p">()</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">keywords_as_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""return keywords as dict of keyword, count in reverse sorted order (descending)"""</span>
|
||||
@@ -823,8 +825,8 @@
|
||||
<span class="s2">"creation_date"</span><span class="p">:</span> <span class="n">album</span><span class="p">[</span><span class="mi">8</span><span class="p">],</span>
|
||||
<span class="s2">"start_date"</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span> <span class="c1"># Photos 5 only</span>
|
||||
<span class="s2">"end_date"</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span> <span class="c1"># Photos 5 only</span>
|
||||
<span class="s2">"customsortascending"</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span> <span class="c1"># Photos 5 only</span>
|
||||
<span class="s2">"customsortkey"</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span> <span class="c1"># Photos 5 only</span>
|
||||
<span class="s2">"customsortascending"</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span> <span class="c1"># Photos 5 only</span>
|
||||
<span class="s2">"customsortkey"</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span> <span class="c1"># Photos 5 only</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="c1"># get details about folders</span>
|
||||
@@ -1137,7 +1139,9 @@
|
||||
<span class="c1"># get info on special types</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"specialType"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"masterModelID"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">26</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"pk"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">26</span><span class="p">]</span> <span class="c1"># same as masterModelID, to match Photos 5</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"pk"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span>
|
||||
<span class="mi">26</span>
|
||||
<span class="p">]</span> <span class="c1"># same as masterModelID, to match Photos 5</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"panorama"</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span> <span class="k">if</span> <span class="n">row</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o">==</span> <span class="mi">1</span> <span class="k">else</span> <span class="kc">False</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"slow_mo"</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span> <span class="k">if</span> <span class="n">row</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o">==</span> <span class="mi">2</span> <span class="k">else</span> <span class="kc">False</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"time_lapse"</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span> <span class="k">if</span> <span class="n">row</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o">==</span> <span class="mi">3</span> <span class="k">else</span> <span class="kc">False</span>
|
||||
@@ -1228,6 +1232,9 @@
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"import_uuid"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">44</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"fok_import_session"</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># photos 5+ only, for shared photos</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"cloudownerhashedpersonid"</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># compute signatures for finding possible duplicates</span>
|
||||
<span class="n">signature</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_duplicate_signature</span><span class="p">(</span><span class="n">uuid</span><span class="p">)</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
@@ -1956,7 +1963,8 @@
|
||||
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZTRASHEDDATE,</span>
|
||||
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZSAVEDASSETTYPE,</span>
|
||||
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZADDEDDATE,</span>
|
||||
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.Z_PK</span>
|
||||
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.Z_PK,</span>
|
||||
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZCLOUDOWNERHASHEDPERSONID</span>
|
||||
<span class="s2"> FROM </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2"> </span>
|
||||
<span class="s2"> JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.Z_PK </span>
|
||||
<span class="s2"> ORDER BY </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZUUID """</span>
|
||||
@@ -2006,6 +2014,7 @@
|
||||
<span class="c1"># 40 ZGENERICASSET.ZSAVEDASSETTYPE -- how item imported</span>
|
||||
<span class="c1"># 41 ZGENERICASSET.ZADDEDDATE -- date item added to the library</span>
|
||||
<span class="c1"># 42 ZGENERICASSET.Z_PK -- primary key</span>
|
||||
<span class="c1"># 43 ZGENERICASSET.ZCLOUDOWNERHASHEDPERSONID -- used to look up owner name (for shared photos)</span>
|
||||
|
||||
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">c</span><span class="p">:</span>
|
||||
<span class="n">uuid</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
|
||||
@@ -2191,6 +2200,7 @@
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"added_date"</span><span class="p">]</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">(</span><span class="mi">1970</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
|
||||
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"pk"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">42</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"cloudownerhashedpersonid"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">43</span><span class="p">]</span>
|
||||
|
||||
<span class="c1"># initialize import session info which will be filled in later</span>
|
||||
<span class="c1"># not every photo has an import session so initialize all records now</span>
|
||||
@@ -3319,9 +3329,9 @@
|
||||
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">regex</span><span class="p">:</span>
|
||||
<span class="n">flags</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">IGNORECASE</span> <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">ignore_case</span> <span class="k">else</span> <span class="mi">0</span>
|
||||
<span class="n">render_options</span> <span class="o">=</span> <span class="n">RenderOptions</span><span class="p">(</span><span class="n">none_str</span><span class="o">=</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="n">photo_list</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="k">for</span> <span class="n">regex</span><span class="p">,</span> <span class="n">template</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">regex</span><span class="p">:</span>
|
||||
<span class="n">regex</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">regex</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span>
|
||||
<span class="n">photo_list</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span><span class="p">:</span>
|
||||
<span class="n">rendered</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">p</span><span class="o">.</span><span class="n">render_template</span><span class="p">(</span><span class="n">template</span><span class="p">,</span> <span class="n">render_options</span><span class="p">)</span>
|
||||
<span class="k">for</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">rendered</span><span class="p">:</span>
|
||||
@@ -3387,6 +3397,10 @@
|
||||
|
||||
<span class="k">return</span> <span class="n">photos</span></div>
|
||||
|
||||
<div class="viewcode-block" id="PhotosDB.execute"><a class="viewcode-back" href="../../../reference.html#osxphotos.PhotosDB.execute">[docs]</a> <span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">sql</span><span class="p">):</span>
|
||||
<span class="sd">"""Execute sql statement and return cursor"""</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db_connection</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">sql</span><span class="p">)</span></div>
|
||||
|
||||
<span class="k">def</span> <span class="nf">_duplicate_signature</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="p">):</span>
|
||||
<span class="sd">"""Compute a signature for finding possible duplicates"""</span>
|
||||
<span class="k">return</span> <span class="p">(</span>
|
||||
@@ -3412,7 +3426,11 @@
|
||||
<span class="sd">"""Returns number of photos in the database</span>
|
||||
<span class="sd"> Includes recently deleted photos and non-selected burst images</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">)</span></div>
|
||||
<span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="fm">__del__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s2">"_db_connection"</span><span class="p">,</span> <span class="kc">None</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_db_connection</span><span class="o">.</span><span class="n">close</span><span class="p">()</span></div>
|
||||
|
||||
|
||||
<span class="k">def</span> <span class="nf">_get_photos_by_attribute</span><span class="p">(</span><span class="n">photos</span><span class="p">,</span> <span class="n">attribute</span><span class="p">,</span> <span class="n">values</span><span class="p">,</span> <span class="n">ignore_case</span><span class="p">):</span>
|
||||
|
||||
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.42.84',
|
||||
VERSION: '0.42.93',
|
||||
LANGUAGE: 'None',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos command line interface (CLI) — osxphotos 0.42.84 documentation</title>
|
||||
<title>osxphotos command line interface (CLI) — osxphotos 0.42.93 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
@@ -1561,7 +1561,7 @@ if more than one option is provided, they are treated as “AND”
|
||||
</div>
|
||||
<div class="section" id="osxphotos-repl">
|
||||
<h3>repl<a class="headerlink" href="#osxphotos-repl" title="Permalink to this headline">¶</a></h3>
|
||||
<p>Run interactive osxphotos shell</p>
|
||||
<p>Run interactive osxphotos REPL shell (useful for debugging, prototyping, and inspecting your Photos library)</p>
|
||||
<div class="highlight-shell notranslate"><div class="highlight"><pre><span></span>osxphotos repl <span class="o">[</span>OPTIONS<span class="o">]</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
@@ -1572,6 +1572,12 @@ if more than one option is provided, they are treated as “AND”
|
||||
<dd><p>Specify Photos database path. Path to Photos library/database can be specified using either –db or directly as PHOTOS_LIBRARY positional argument. If neither –db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-emacs">
|
||||
<span class="sig-name descname"><span class="pre">--emacs</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-repl-emacs" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Launch REPL with Emacs keybindings (default is vi bindings)</p>
|
||||
</dd></dl>
|
||||
|
||||
</div>
|
||||
<div class="section" id="osxphotos-tutorial">
|
||||
<h3>tutorial<a class="headerlink" href="#osxphotos-tutorial" title="Permalink to this headline">¶</a></h3>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Index — osxphotos 0.42.84 documentation</title>
|
||||
<title>Index — osxphotos 0.42.93 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
@@ -254,6 +254,13 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-edited-suffix">osxphotos-export command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--emacs
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-emacs">osxphotos-repl command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1333,14 +1340,16 @@
|
||||
<h2 id="E">E</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.execute">execute() (osxphotos.PhotosDB method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.exif_info">exif_info (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.exiftool">exiftool (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.export">export() (osxphotos.PhotoInfo method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.export">export() (osxphotos.PhotoInfo method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.export2">export2() (osxphotos.PhotoInfo method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.ExifInfo.exposure_bias">exposure_bias (osxphotos.PhotoInfo.ExifInfo attribute)</a>
|
||||
@@ -2102,6 +2111,8 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-db">--db <Photos database path></a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-emacs">--emacs</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -2112,6 +2123,8 @@
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.ScoreInfo.overall">overall (osxphotos.PhotoInfo.ScoreInfo attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.owner">owner (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Welcome to osxphotos’s documentation! — osxphotos 0.42.84 documentation</title>
|
||||
<title>Welcome to osxphotos’s documentation! — osxphotos 0.42.93 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>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos — osxphotos 0.42.84 documentation</title>
|
||||
<title>osxphotos — osxphotos 0.42.93 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>
|
||||
|
||||
BIN
docs/objects.inv
BIN
docs/objects.inv
Binary file not shown.
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos package — osxphotos 0.42.84 documentation</title>
|
||||
<title>osxphotos package — osxphotos 0.42.93 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
@@ -90,6 +90,12 @@ valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict
|
||||
<dd><p>return the database version as stored in LiGlobals table</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.execute">
|
||||
<span class="sig-name descname"><span class="pre">execute</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">sql</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photosdb/photosdb.html#PhotosDB.execute"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotosDB.execute" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Execute sql statement and return cursor</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotosDB.folder_info">
|
||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">folder_info</span></span><a class="headerlink" href="#osxphotos.PhotosDB.folder_info" title="Permalink to this definition">¶</a></dt>
|
||||
@@ -1143,6 +1149,12 @@ Photos 5 mangles filenames upon import</p>
|
||||
<dd><p>returns width of the original photo version in pixels</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.owner">
|
||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">owner</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.owner" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Return name of photo owner for shared photos (Photos 5+ only), or None if not shared</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.panorama">
|
||||
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">panorama</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.panorama" title="Permalink to this definition">¶</a></dt>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Search — osxphotos 0.42.84 documentation</title>
|
||||
<title>Search — osxphotos 0.42.93 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.42.84"
|
||||
__version__ = "0.42.93"
|
||||
|
||||
@@ -22,6 +22,7 @@ from ._constants import (
|
||||
AlbumSortOrder,
|
||||
)
|
||||
from .datetime_utils import get_local_tz
|
||||
from .query_builder import get_query
|
||||
|
||||
|
||||
def sort_list_by_keys(values, sort_keys):
|
||||
@@ -131,6 +132,28 @@ class AlbumInfoBaseClass:
|
||||
def photos(self):
|
||||
return []
|
||||
|
||||
@property
|
||||
def owner(self):
|
||||
"""Return name of photo owner for shared album (Photos 5+ only), or None if not shared"""
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
return None
|
||||
|
||||
try:
|
||||
return self._owner
|
||||
except AttributeError:
|
||||
try:
|
||||
personid = self._db._dbalbum_details[self.uuid][
|
||||
"cloudownerhashedpersonid"
|
||||
]
|
||||
self._owner = (
|
||||
self._db._db_hashed_person_id[personid]["full_name"]
|
||||
if personid
|
||||
else None
|
||||
)
|
||||
except KeyError:
|
||||
self._owner = None
|
||||
return self._owner
|
||||
|
||||
def __len__(self):
|
||||
"""return number of photos contained in album"""
|
||||
return len(self.photos)
|
||||
|
||||
112
osxphotos/cli.py
112
osxphotos/cli.py
@@ -55,10 +55,11 @@ from .exiftool import get_exiftool_path
|
||||
from .export_db import ExportDB, ExportDBInMemory
|
||||
from .fileutil import FileUtil, FileUtilNoOp
|
||||
from .path_utils import is_valid_filepath, sanitize_filename, sanitize_filepath
|
||||
from .photoinfo import ExportResults
|
||||
from .photoinfo import ExportResults, PhotoInfo
|
||||
from .photokit import check_photokit_authorization, request_photokit_authorization
|
||||
from .photosalbum import PhotosAlbum
|
||||
from .phototemplate import PhotoTemplate, RenderOptions
|
||||
from .pyrepl import embed_repl
|
||||
from .queryoptions import QueryOptions
|
||||
from .uti import get_preferred_uti_extension
|
||||
from .utils import expand_and_validate_filepath, load_function
|
||||
@@ -4011,6 +4012,32 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
osxphotos uses the following 3rd party software licensed under the BSD-3-Clause License:
|
||||
Click (Copyright 2014 Pallets), ptpython (Copyright (c) 2015, Jonathan Slenders)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are
|
||||
permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list
|
||||
of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
of conditions and the following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
used to endorse or promote products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
click.echo(f"osxphotos, version {__version__}")
|
||||
click.echo("")
|
||||
@@ -4032,7 +4059,7 @@ def tutorial(ctx, cli_obj, width):
|
||||
click.echo_via_pager(tutorial_help(width=width))
|
||||
|
||||
|
||||
def _show_photo(photo):
|
||||
def _show_photo(photo: PhotoInfo):
|
||||
"""open image with default image viewer
|
||||
|
||||
Note: This is for debugging only -- it will actually open any filetype which could
|
||||
@@ -4078,16 +4105,34 @@ def _get_selected(photosdb):
|
||||
return get_selected
|
||||
|
||||
|
||||
def _spotlight_photo(photo: PhotoInfo):
|
||||
photo_ = photoscript.Photo(photo.uuid)
|
||||
photo_.spotlight()
|
||||
|
||||
|
||||
@cli.command()
|
||||
@DB_OPTION
|
||||
@click.pass_obj
|
||||
@click.pass_context
|
||||
def repl(ctx, cli_obj, db):
|
||||
"""Run interactive osxphotos shell"""
|
||||
@click.option(
|
||||
"--emacs",
|
||||
required=False,
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Launch REPL with Emacs keybindings (default is vi bindings)",
|
||||
)
|
||||
def repl(ctx, cli_obj, db, emacs):
|
||||
"""Run interactive osxphotos REPL shell (useful for debugging, prototyping, and inspecting your Photos library)"""
|
||||
|
||||
from osxphotos import PhotosDB, PhotoInfo, ExifTool
|
||||
from objexplore import explore
|
||||
from photoscript import Album, Photo, PhotosLibrary
|
||||
from rich import inspect as _inspect
|
||||
|
||||
from osxphotos import ExifTool, PhotoInfo, PhotosDB
|
||||
from osxphotos.albuminfo import AlbumInfo
|
||||
from osxphotos.placeinfo import PlaceInfo
|
||||
from osxphotos.queryoptions import QueryOptions
|
||||
|
||||
pretty.install()
|
||||
print(f"python version: {sys.version}")
|
||||
print(f"osxphotos version: {osxphotos._version.__version__}")
|
||||
@@ -4102,24 +4147,21 @@ def repl(ctx, cli_obj, db):
|
||||
# shortcut for helper functions
|
||||
get_photo = photosdb.get_photo
|
||||
show = _show_photo
|
||||
spotlight = _spotlight_photo
|
||||
get_selected = _get_selected(photosdb)
|
||||
selected = get_selected()
|
||||
try:
|
||||
selected = get_selected()
|
||||
except Exception:
|
||||
# get_selected sometimes fails
|
||||
selected = []
|
||||
|
||||
def inspect(obj):
|
||||
"""inspect object"""
|
||||
return _inspect(obj, methods=True)
|
||||
|
||||
class ReprQuit:
|
||||
def __repr__(self):
|
||||
sys.exit(0)
|
||||
|
||||
def __call__(self):
|
||||
sys.exit(0)
|
||||
|
||||
quit = ReprQuit()
|
||||
q = ReprQuit()
|
||||
|
||||
print(f"Found {len(photos)} photos in {tictoc:0.2f} seconds")
|
||||
print(f"Found {len(photos)} photos in {tictoc:0.2f} seconds\n")
|
||||
print("The following classes have been imported from osxphotos:")
|
||||
print("- AlbumInfo, ExifTool, PhotoInfo, PhotosDB, PlaceInfo, QueryOptions\n")
|
||||
print("The following variables are defined:")
|
||||
print(f"- photosdb: PhotosDB() instance for {photosdb.library_path}")
|
||||
print(
|
||||
@@ -4129,16 +4171,34 @@ def repl(ctx, cli_obj, db):
|
||||
f"- selected: list of PhotoInfo objects for any photos selected in Photos (len={len(selected)})"
|
||||
)
|
||||
print(f"\nThe following functions may be helpful:")
|
||||
print(f"- get_photo(uuid): return a PhotoInfo object for photo with uuid")
|
||||
print(
|
||||
f"- get_selected(): return list of PhotoInfo objects for photos selected in Photos"
|
||||
)
|
||||
print(f"- show(photo): open a photo object in the default viewer")
|
||||
print(
|
||||
f"- help(object): print help text including list of methods for object; for example, help(PhotosDB)"
|
||||
f"- get_photo(uuid): return a PhotoInfo object for photo with uuid; e.g. get_photo('B13F4485-94E0-41CD-AF71-913095D62E31')"
|
||||
)
|
||||
print(
|
||||
f"- inspect(object): print information about an object; for example inspect(photosdb)"
|
||||
f"- get_selected(); return list of PhotoInfo objects for photos selected in Photos"
|
||||
)
|
||||
print(
|
||||
f"- show(photo): open a photo object in the default viewer; e.g. show(selected[0])"
|
||||
)
|
||||
print(
|
||||
f"- show(path): open a file at path in the default viewer; e.g. show('/path/to/photo.jpg')"
|
||||
)
|
||||
print(f"- spotlight(photo): open a photo and spotlight it in Photos")
|
||||
# print(
|
||||
# f"- help(object): print help text including list of methods for object; for example, help(PhotosDB)"
|
||||
# )
|
||||
print(
|
||||
f"- inspect(object): print information about an object; e.g. inspect(PhotoInfo)"
|
||||
)
|
||||
print(
|
||||
f"- explore(object): interactively explore an object with objexplore; e.g. explore(PhotoInfo)"
|
||||
)
|
||||
print(f"- q, quit, quit(), exit, exit(): exit this interactive shell\n")
|
||||
|
||||
embed_repl(
|
||||
globals=globals(),
|
||||
locals=locals(),
|
||||
history_filename=str(pathlib.Path.home() / ".osxphotos_repl_history"),
|
||||
quit_words=["q", "quit", "exit"],
|
||||
vi_mode=not emacs,
|
||||
)
|
||||
print(f"- q, quit, or quit(): exit this interactive shell\n")
|
||||
code.interact(banner="", local=locals())
|
||||
|
||||
@@ -912,7 +912,11 @@ def export2(
|
||||
preview_path = pathlib.Path(self.path_derivatives[0])
|
||||
preview_ext = preview_path.suffix
|
||||
preview_name = dest.parent / f"{dest.stem}{preview_suffix}{preview_ext}"
|
||||
preview_name = pathlib.Path(increment_filename(preview_name))
|
||||
preview_name = (
|
||||
preview_name
|
||||
if overwrite
|
||||
else pathlib.Path(increment_filename(preview_name))
|
||||
)
|
||||
if preview_path is not None:
|
||||
results = self._export_photo(
|
||||
preview_path,
|
||||
|
||||
@@ -38,6 +38,7 @@ from ..albuminfo import AlbumInfo, ImportInfo
|
||||
from ..personinfo import FaceInfo, PersonInfo
|
||||
from ..phototemplate import PhotoTemplate, RenderOptions
|
||||
from ..placeinfo import PlaceInfo4, PlaceInfo5
|
||||
from ..query_builder import get_query
|
||||
from ..text_detection import detect_text
|
||||
from ..uti import get_preferred_uti_extension, get_uti_for_extension
|
||||
from ..utils import _debug, _get_resource_loc, findfiles
|
||||
@@ -1098,6 +1099,26 @@ class PhotoInfo:
|
||||
logging.warning(f"Did not find signature for {self.uuid} in _db_signatures")
|
||||
return duplicates
|
||||
|
||||
@property
|
||||
def owner(self):
|
||||
"""Return name of photo owner for shared photos (Photos 5+ only), or None if not shared"""
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
return None
|
||||
|
||||
try:
|
||||
return self._owner
|
||||
except AttributeError:
|
||||
try:
|
||||
personid = self._info["cloudownerhashedpersonid"]
|
||||
self._owner = (
|
||||
self._db._db_hashed_person_id[personid]["full_name"]
|
||||
if personid
|
||||
else None
|
||||
)
|
||||
except KeyError:
|
||||
self._owner = None
|
||||
return self._owner
|
||||
|
||||
def render_template(
|
||||
self, template_str: str, options: Optional[RenderOptions] = None
|
||||
):
|
||||
|
||||
@@ -70,12 +70,24 @@ def _process_comments_5(photosdb):
|
||||
results = conn.execute(
|
||||
"""
|
||||
SELECT DISTINCT
|
||||
ZINVITEEHASHEDPERSONID,
|
||||
ZINVITEEFIRSTNAME,
|
||||
ZINVITEELASTNAME,
|
||||
ZINVITEEFULLNAME
|
||||
FROM
|
||||
ZCLOUDSHAREDALBUMINVITATIONRECORD
|
||||
ZINVITEEHASHEDPERSONID AS HASHEDPERSONID,
|
||||
ZINVITEEFIRSTNAME AS FIRSTNAME,
|
||||
ZINVITEELASTNAME AS LASTNAME,
|
||||
ZINVITEEFULLNAME AS FULLNAME
|
||||
FROM ZCLOUDSHAREDALBUMINVITATIONRECORD
|
||||
WHERE HASHEDPERSONID IS NOT NULL
|
||||
AND HASHEDPERSONID != ""
|
||||
AND NOT (FIRSTNAME IS NULL AND LASTNAME IS NULL)
|
||||
UNION
|
||||
SELECT DISTINCT
|
||||
ZCLOUDOWNERHASHEDPERSONID AS HASHEDPERSONID,
|
||||
ZCLOUDOWNERFIRSTNAME AS FIRSTNAME,
|
||||
ZCLOUDOWNERLASTNAME AS LASTNAME,
|
||||
ZCLOUDOWNERFULLNAME AS FULLNAME
|
||||
FROM ZGENERICALBUM
|
||||
WHERE HASHEDPERSONID IS NOT NULL
|
||||
AND HASHEDPERSONID != ""
|
||||
AND NOT (FIRSTNAME IS NULL AND LASTNAME IS NULL)
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -148,10 +160,10 @@ def _process_comments_5(photosdb):
|
||||
db_comments["comments"].append(CommentInfo(dt, user_name, ismine, text))
|
||||
|
||||
# sort results
|
||||
for uuid in photosdb._db_comments_uuid:
|
||||
for uuid, value in photosdb._db_comments_uuid.items():
|
||||
if photosdb._db_comments_uuid[uuid]["likes"]:
|
||||
photosdb._db_comments_uuid[uuid]["likes"].sort(key=lambda x: x.datetime)
|
||||
if photosdb._db_comments_uuid[uuid]["comments"]:
|
||||
photosdb._db_comments_uuid[uuid]["comments"].sort(key=lambda x: x.datetime)
|
||||
value["comments"].sort(key=lambda x: x.datetime)
|
||||
|
||||
conn.close()
|
||||
|
||||
@@ -330,6 +330,8 @@ class PhotosDB:
|
||||
else:
|
||||
self._process_database5()
|
||||
|
||||
self._db_connection, _ = self.get_db_connection()
|
||||
|
||||
@property
|
||||
def keywords_as_dict(self):
|
||||
"""return keywords as dict of keyword, count in reverse sorted order (descending)"""
|
||||
@@ -790,8 +792,8 @@ class PhotosDB:
|
||||
"creation_date": album[8],
|
||||
"start_date": None, # Photos 5 only
|
||||
"end_date": None, # Photos 5 only
|
||||
"customsortascending": None, # Photos 5 only
|
||||
"customsortkey": None, # Photos 5 only
|
||||
"customsortascending": None, # Photos 5 only
|
||||
"customsortkey": None, # Photos 5 only
|
||||
}
|
||||
|
||||
# get details about folders
|
||||
@@ -1104,7 +1106,9 @@ class PhotosDB:
|
||||
# get info on special types
|
||||
self._dbphotos[uuid]["specialType"] = row[25]
|
||||
self._dbphotos[uuid]["masterModelID"] = row[26]
|
||||
self._dbphotos[uuid]["pk"] = row[26] # same as masterModelID, to match Photos 5
|
||||
self._dbphotos[uuid]["pk"] = row[
|
||||
26
|
||||
] # same as masterModelID, to match Photos 5
|
||||
self._dbphotos[uuid]["panorama"] = True if row[25] == 1 else False
|
||||
self._dbphotos[uuid]["slow_mo"] = True if row[25] == 2 else False
|
||||
self._dbphotos[uuid]["time_lapse"] = True if row[25] == 3 else False
|
||||
@@ -1195,6 +1199,9 @@ class PhotosDB:
|
||||
self._dbphotos[uuid]["import_uuid"] = row[44]
|
||||
self._dbphotos[uuid]["fok_import_session"] = None
|
||||
|
||||
# photos 5+ only, for shared photos
|
||||
self._dbphotos[uuid]["cloudownerhashedpersonid"] = None
|
||||
|
||||
# compute signatures for finding possible duplicates
|
||||
signature = self._duplicate_signature(uuid)
|
||||
try:
|
||||
@@ -1923,7 +1930,8 @@ class PhotosDB:
|
||||
{asset_table}.ZTRASHEDDATE,
|
||||
{asset_table}.ZSAVEDASSETTYPE,
|
||||
{asset_table}.ZADDEDDATE,
|
||||
{asset_table}.Z_PK
|
||||
{asset_table}.Z_PK,
|
||||
{asset_table}.ZCLOUDOWNERHASHEDPERSONID
|
||||
FROM {asset_table}
|
||||
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
|
||||
ORDER BY {asset_table}.ZUUID """
|
||||
@@ -1973,6 +1981,7 @@ class PhotosDB:
|
||||
# 40 ZGENERICASSET.ZSAVEDASSETTYPE -- how item imported
|
||||
# 41 ZGENERICASSET.ZADDEDDATE -- date item added to the library
|
||||
# 42 ZGENERICASSET.Z_PK -- primary key
|
||||
# 43 ZGENERICASSET.ZCLOUDOWNERHASHEDPERSONID -- used to look up owner name (for shared photos)
|
||||
|
||||
for row in c:
|
||||
uuid = row[0]
|
||||
@@ -2158,6 +2167,7 @@ class PhotosDB:
|
||||
info["added_date"] = datetime(1970, 1, 1)
|
||||
|
||||
info["pk"] = row[42]
|
||||
info["cloudownerhashedpersonid"] = row[43]
|
||||
|
||||
# initialize import session info which will be filled in later
|
||||
# not every photo has an import session so initialize all records now
|
||||
@@ -3286,9 +3296,9 @@ class PhotosDB:
|
||||
if options.regex:
|
||||
flags = re.IGNORECASE if options.ignore_case else 0
|
||||
render_options = RenderOptions(none_str="")
|
||||
photo_list = []
|
||||
for regex, template in options.regex:
|
||||
regex = re.compile(regex, flags)
|
||||
photo_list = []
|
||||
for p in photos:
|
||||
rendered, _ = p.render_template(template, render_options)
|
||||
for value in rendered:
|
||||
@@ -3354,6 +3364,10 @@ class PhotosDB:
|
||||
|
||||
return photos
|
||||
|
||||
def execute(self, sql):
|
||||
"""Execute sql statement and return cursor"""
|
||||
return self._db_connection.cursor().execute(sql)
|
||||
|
||||
def _duplicate_signature(self, uuid):
|
||||
"""Compute a signature for finding possible duplicates"""
|
||||
return (
|
||||
@@ -3381,6 +3395,10 @@ class PhotosDB:
|
||||
"""
|
||||
return len(self._dbphotos)
|
||||
|
||||
def __del__(self):
|
||||
if getattr(self, "_db_connection", None):
|
||||
self._db_connection.close()
|
||||
|
||||
|
||||
def _get_photos_by_attribute(photos, attribute, values, ignore_case):
|
||||
"""Search for photos based on values being in PhotoInfo.attribute
|
||||
|
||||
131
osxphotos/pyrepl.py
Normal file
131
osxphotos/pyrepl.py
Normal file
@@ -0,0 +1,131 @@
|
||||
""" Custom Python REPL based on ptpython that allows quitting with custom keywords instead of `quit()` """
|
||||
|
||||
""" This file is distributed under the same license as the ptpython package:
|
||||
|
||||
Copyright (c) 2015, Jonathan Slenders (ptpython), (c) 2021 Rhet Turnbull (this file)
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the {organization} nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
from ptpython.repl import (
|
||||
ContextManager,
|
||||
DummyContext,
|
||||
PythonRepl,
|
||||
builtins,
|
||||
patch_stdout_context,
|
||||
)
|
||||
|
||||
|
||||
class PyReplQuitter(PythonRepl):
|
||||
"""Custom pypython repl that allows quitting REPL with custom commands"""
|
||||
|
||||
def __init__(self, *args, quit_words: Optional[List[str]] = None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.quit_words = quit_words or ["quit", "q"]
|
||||
|
||||
def eval(self, line: str) -> object:
|
||||
if line.strip() in self.quit_words:
|
||||
sys.exit(0)
|
||||
return super().eval(line)
|
||||
|
||||
|
||||
def embed_repl(
|
||||
globals=None,
|
||||
locals=None,
|
||||
configure: Optional[Callable[[PythonRepl], None]] = None,
|
||||
vi_mode: bool = False,
|
||||
history_filename: Optional[str] = None,
|
||||
title: Optional[str] = None,
|
||||
startup_paths=None,
|
||||
patch_stdout: bool = False,
|
||||
return_asyncio_coroutine: bool = False,
|
||||
quit_words: Optional[List[str]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Call this to embed Python shell at the current point in your program.
|
||||
It's similar to `IPython.embed` and `bpython.embed`. ::
|
||||
from prompt_toolkit.contrib.repl import embed
|
||||
embed(globals(), locals())
|
||||
:param vi_mode: Boolean. Use Vi instead of Emacs key bindings.
|
||||
:param configure: Callable that will be called with the `PythonRepl` as a first
|
||||
argument, to trigger configuration.
|
||||
:param title: Title to be displayed in the terminal titlebar. (None or string.)
|
||||
:param patch_stdout: When true, patch `sys.stdout` so that background
|
||||
threads that are printing will print nicely above the prompt.
|
||||
"""
|
||||
# Default globals/locals
|
||||
if globals is None:
|
||||
globals = {
|
||||
"__name__": "__main__",
|
||||
"__package__": None,
|
||||
"__doc__": None,
|
||||
"__builtins__": builtins,
|
||||
}
|
||||
|
||||
locals = locals or globals
|
||||
|
||||
def get_globals():
|
||||
return globals
|
||||
|
||||
def get_locals():
|
||||
return locals
|
||||
|
||||
# Create REPL.
|
||||
repl = PyReplQuitter(
|
||||
get_globals=get_globals,
|
||||
get_locals=get_locals,
|
||||
vi_mode=vi_mode,
|
||||
history_filename=history_filename,
|
||||
startup_paths=startup_paths,
|
||||
quit_words=quit_words,
|
||||
)
|
||||
|
||||
if title:
|
||||
repl.terminal_title = title
|
||||
|
||||
if configure:
|
||||
configure(repl)
|
||||
|
||||
# Start repl.
|
||||
patch_context: ContextManager = (
|
||||
patch_stdout_context() if patch_stdout else DummyContext()
|
||||
)
|
||||
|
||||
if return_asyncio_coroutine:
|
||||
|
||||
async def coroutine():
|
||||
with patch_context:
|
||||
await repl.run_async()
|
||||
|
||||
return coroutine()
|
||||
else:
|
||||
with patch_context:
|
||||
repl.run()
|
||||
5
osxphotos/queries/README.md
Normal file
5
osxphotos/queries/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Query templates
|
||||
|
||||
This folder contains sql query templates for getting various photo properties
|
||||
|
||||
The query templates must be rendered with mako (see query_builder.py)
|
||||
4
osxphotos/queries/cloud_album_owner.sql.mako
Normal file
4
osxphotos/queries/cloud_album_owner.sql.mako
Normal file
@@ -0,0 +1,4 @@
|
||||
-- Get owner name for shared iCloud album
|
||||
SELECT ZGENERICALBUM.ZCLOUDOWNERFULLNAME AS OWNER_FULLNAME
|
||||
FROM ZGENERICALBUM
|
||||
WHERE ZGENERICALBUM.ZUUID = '${uuid}'
|
||||
23
osxphotos/queries/shared_owner.sql.mako
Normal file
23
osxphotos/queries/shared_owner.sql.mako
Normal file
@@ -0,0 +1,23 @@
|
||||
-- Get the owner name of person who owns a photo in a shared album
|
||||
--
|
||||
-- Case where someone has invited you to a shared album
|
||||
-- Need to get the owner of the shared album
|
||||
SELECT DISTINCT
|
||||
ZGENERICALBUM.ZCLOUDOWNERFULLNAME as OWNER_FULLNAME
|
||||
FROM ZGENERICALBUM
|
||||
JOIN ${asset_table} ON ${asset_table}.ZCLOUDOWNERHASHEDPERSONID = ZGENERICALBUM.ZCLOUDOWNERHASHEDPERSONID
|
||||
WHERE ${asset_table}.ZUUID = "${uuid}"
|
||||
AND ZGENERICALBUM.ZCLOUDOWNERHASHEDPERSONID IS NOT NULL
|
||||
AND ZGENERICALBUM.ZCLOUDOWNERHASHEDPERSONID != ""
|
||||
AND OWNER_FULLNAME != "(null) (null)"
|
||||
UNION
|
||||
-- Case where you have invited someone to a shared album
|
||||
-- Need to get the data for person who was invited to the album
|
||||
SELECT DISTINCT
|
||||
ZCLOUDSHAREDALBUMINVITATIONRECORD.ZINVITEEFULLNAME AS OWNER_FULLNAME
|
||||
FROM ZCLOUDSHAREDALBUMINVITATIONRECORD
|
||||
JOIN ${asset_table} ON ${asset_table}.ZCLOUDOWNERHASHEDPERSONID = ZCLOUDSHAREDALBUMINVITATIONRECORD.ZINVITEEHASHEDPERSONID
|
||||
WHERE ${asset_table}.ZUUID = "${uuid}"
|
||||
AND ZCLOUDSHAREDALBUMINVITATIONRECORD.ZINVITEEHASHEDPERSONID IS NOT NULL
|
||||
AND ZCLOUDSHAREDALBUMINVITATIONRECORD.ZINVITEEHASHEDPERSONID != ""
|
||||
AND OWNER_FULLNAME != "(null) (null)"
|
||||
6
osxphotos/queries/title.sql.mako
Normal file
6
osxphotos/queries/title.sql.mako
Normal file
@@ -0,0 +1,6 @@
|
||||
-- Get title of a photo with given UUID
|
||||
SELECT
|
||||
ZADDITIONALASSETATTRIBUTES.ZTITLE
|
||||
FROM ZADDITIONALASSETATTRIBUTES
|
||||
JOIN ${asset_table} ON ${asset_table}.Z_PK = ZADDITIONALASSETATTRIBUTES.ZASSET
|
||||
WHERE ${asset_table}.ZUUID = "${uuid}"
|
||||
36
osxphotos/query_builder.py
Normal file
36
osxphotos/query_builder.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""Build sql queries from template to retrieve info from the database"""
|
||||
|
||||
import os.path
|
||||
import pathlib
|
||||
from functools import lru_cache
|
||||
|
||||
from mako.template import Template
|
||||
|
||||
from ._constants import _DB_TABLE_NAMES
|
||||
|
||||
QUERY_DIR = os.path.join(os.path.dirname(__file__), "queries")
|
||||
|
||||
|
||||
def get_query(query_name, photos_ver, **kwargs):
|
||||
"""Return sqlite query string for an attribute and a given database version"""
|
||||
|
||||
# there can be a single query for multiple database versions or separate queries for each version
|
||||
# try generic version first (most common case), if that fails, look for version specific query
|
||||
query_string = _get_query_string(query_name, photos_ver)
|
||||
asset_table = _DB_TABLE_NAMES[photos_ver]["ASSET"]
|
||||
query_template = Template(query_string)
|
||||
return query_template.render(asset_table=asset_table, **kwargs)
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _get_query_string(query_name, photos_ver):
|
||||
"""Return sqlite query string for an attribute and a given database version"""
|
||||
query_file = pathlib.Path(QUERY_DIR) / f"{query_name}.sql.mako"
|
||||
if not query_file.is_file():
|
||||
query_file = pathlib.Path(QUERY_DIR) / f"{query_name}_{photos_ver}.sql.mako"
|
||||
if not query_file.is_file():
|
||||
raise FileNotFoundError(f"Query file '{query_file}' not found")
|
||||
|
||||
with open(query_file, "r") as f:
|
||||
query_string = f.read()
|
||||
return query_string
|
||||
@@ -1,4 +1,5 @@
|
||||
pyobjc-core>=7.2,<8.0
|
||||
pyobjc-framework-Cocoa>=7.2,<8.0
|
||||
pyobjc-framework-AppleScriptKit>=7.2,<8.0
|
||||
pyobjc-framework-AppleScriptObjC>=7.2,<8.0
|
||||
pyobjc-framework-Photos>=7.2,<8.0
|
||||
@@ -18,6 +19,8 @@ photoscript>=0.1.4,<0.2.0
|
||||
toml>=0.10.2,<0.11.0
|
||||
osxmetadata>=0.99.33,<1.0.0
|
||||
textx>=2.3.0,<2.4.0
|
||||
rich>=10.6.0,<11.0.0
|
||||
rich>=10.6.0,<=11.0.0
|
||||
bitmath>=1.3.3.1,<1.4.0.0
|
||||
more-itertools>=8.8.0,<9.0.0
|
||||
objexplore>=1.5.5,<1.6.0
|
||||
ptpython>=3.0.20,<3.1.0
|
||||
6
setup.py
6
setup.py
@@ -70,10 +70,12 @@ setup(
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
],
|
||||
install_requires=[
|
||||
"pyobjc-core>=7.2,<8.0",
|
||||
"pyobjc-framework-Cocoa>=7.2,<8.0",
|
||||
"pyobjc-framework-AppleScriptKit>=7.2,<8.0",
|
||||
"pyobjc-framework-AppleScriptObjC>=7.2,<8.0",
|
||||
"pyobjc-framework-Photos>=7.2,<8.0",
|
||||
@@ -93,9 +95,11 @@ setup(
|
||||
"toml>=0.10.2,<0.11.0",
|
||||
"osxmetadata>=0.99.33,<1.0.0",
|
||||
"textx>=2.3.0,<2.4.0",
|
||||
"rich>=10.6.0,<11.0.0",
|
||||
"rich>=10.6.0,<=11.0.0",
|
||||
"bitmath>=1.3.3.1,<1.4.0.0",
|
||||
"more-itertools>=8.8.0,<9.0.0",
|
||||
"objexplore>=1.5.5,<1.6.0",
|
||||
"ptpython>=3.0.20,<3.1.0",
|
||||
],
|
||||
entry_points={"console_scripts": ["osxphotos=osxphotos.__main__:cli"]},
|
||||
include_package_data=True,
|
||||
|
||||
@@ -337,7 +337,7 @@ def test_attributes(photosdb):
|
||||
|
||||
|
||||
def test_attributes_2(photosdb):
|
||||
""" Test attributes including height, width, etc """
|
||||
"""Test attributes including height, width, etc"""
|
||||
import datetime
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
|
||||
@@ -517,39 +517,39 @@ def test_count(photosdb):
|
||||
|
||||
|
||||
def test_photos_intrash_1(photosdb):
|
||||
""" test PhotosDB.photos(intrash=True) """
|
||||
"""test PhotosDB.photos(intrash=True)"""
|
||||
photos = photosdb.photos(intrash=True)
|
||||
assert len(photos) == PHOTOS_IN_TRASH_LEN
|
||||
|
||||
|
||||
def test_photos_intrash_2(photosdb):
|
||||
""" test PhotosDB.photos(intrash=True) """
|
||||
"""test PhotosDB.photos(intrash=True)"""
|
||||
photos = photosdb.photos(intrash=True)
|
||||
for p in photos:
|
||||
assert p.intrash
|
||||
|
||||
|
||||
def test_photos_intrash_3(photosdb):
|
||||
""" test PhotosDB.photos(intrash=False) """
|
||||
"""test PhotosDB.photos(intrash=False)"""
|
||||
photos = photosdb.photos(intrash=False)
|
||||
for p in photos:
|
||||
assert not p.intrash
|
||||
|
||||
|
||||
def test_photoinfo_intrash_1(photosdb):
|
||||
""" Test PhotoInfo.intrash """
|
||||
"""Test PhotoInfo.intrash"""
|
||||
p = photosdb.photos(uuid=[UUID_DICT["intrash"]], intrash=True)[0]
|
||||
assert p.intrash
|
||||
|
||||
|
||||
def test_photoinfo_intrash_2(photosdb):
|
||||
""" Test PhotoInfo.intrash and intrash=default"""
|
||||
"""Test PhotoInfo.intrash and intrash=default"""
|
||||
p = photosdb.photos(uuid=[UUID_DICT["intrash"]])
|
||||
assert not p
|
||||
|
||||
|
||||
def test_photoinfo_intrash_3(photosdb):
|
||||
""" Test PhotoInfo.intrash and photo has keyword and person """
|
||||
"""Test PhotoInfo.intrash and photo has keyword and person"""
|
||||
p = photosdb.photos(uuid=[UUID_DICT["intrash_person_keywords"]], intrash=True)[0]
|
||||
assert p.intrash
|
||||
assert "Maria" in p.persons
|
||||
@@ -557,7 +557,7 @@ def test_photoinfo_intrash_3(photosdb):
|
||||
|
||||
|
||||
def test_photoinfo_intrash_4(photosdb):
|
||||
""" Test PhotoInfo.intrash and photo has keyword and person """
|
||||
"""Test PhotoInfo.intrash and photo has keyword and person"""
|
||||
p = photosdb.photos(persons=["Maria"], intrash=True)[0]
|
||||
assert p.intrash
|
||||
assert "Maria" in p.persons
|
||||
@@ -565,7 +565,7 @@ def test_photoinfo_intrash_4(photosdb):
|
||||
|
||||
|
||||
def test_photoinfo_intrash_5(photosdb):
|
||||
""" Test PhotoInfo.intrash and photo has keyword and person """
|
||||
"""Test PhotoInfo.intrash and photo has keyword and person"""
|
||||
p = photosdb.photos(keywords=["wedding"], intrash=True)[0]
|
||||
assert p.intrash
|
||||
assert "Maria" in p.persons
|
||||
@@ -573,7 +573,7 @@ def test_photoinfo_intrash_5(photosdb):
|
||||
|
||||
|
||||
def test_photoinfo_not_intrash(photosdb):
|
||||
""" Test PhotoInfo.intrash """
|
||||
"""Test PhotoInfo.intrash"""
|
||||
p = photosdb.photos(uuid=[UUID_DICT["not_intrash"]])[0]
|
||||
assert not p.intrash
|
||||
|
||||
@@ -594,7 +594,7 @@ def test_keyword_not_in_album(photosdb):
|
||||
|
||||
|
||||
def test_album_folder_name(photosdb):
|
||||
"""Test query with album name same as a folder name """
|
||||
"""Test query with album name same as a folder name"""
|
||||
|
||||
photos = photosdb.photos(albums=["Pumpkin Farm"])
|
||||
assert sorted(p.uuid for p in photos) == sorted(UUID_PUMPKIN_FARM)
|
||||
@@ -617,7 +617,7 @@ def test_get_library_path(photosdb):
|
||||
|
||||
|
||||
def test_get_db_connection(photosdb):
|
||||
""" Test PhotosDB.get_db_connection """
|
||||
"""Test PhotosDB.get_db_connection"""
|
||||
import sqlite3
|
||||
|
||||
conn, cursor = photosdb.get_db_connection()
|
||||
@@ -926,7 +926,7 @@ def test_export_14(caplog, photosdb):
|
||||
|
||||
|
||||
def test_eq(photosdb):
|
||||
""" Test equality of two PhotoInfo objects """
|
||||
"""Test equality of two PhotoInfo objects"""
|
||||
import osxphotos
|
||||
|
||||
photosdb2 = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
@@ -936,7 +936,7 @@ def test_eq(photosdb):
|
||||
|
||||
|
||||
def test_eq_2(photosdb):
|
||||
""" Test equality of two PhotoInfo objects when one has memoized property """
|
||||
"""Test equality of two PhotoInfo objects when one has memoized property"""
|
||||
import osxphotos
|
||||
|
||||
photosdb2 = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
@@ -960,7 +960,7 @@ def test_photosdb_repr():
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photosdb2 = eval(repr(photosdb))
|
||||
|
||||
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name"]
|
||||
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name", "_db_connection"]
|
||||
assert {k: v for k, v in photosdb.__dict__.items() if k not in ignore_keys} == {
|
||||
k: v for k, v in photosdb2.__dict__.items() if k not in ignore_keys
|
||||
}
|
||||
@@ -999,7 +999,7 @@ def test_from_to_date(photosdb):
|
||||
|
||||
|
||||
def test_date_invalid():
|
||||
""" Test date is invalid """
|
||||
"""Test date is invalid"""
|
||||
# doesn't run correctly with the module-level fixture
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import osxphotos
|
||||
@@ -1016,7 +1016,7 @@ def test_date_invalid():
|
||||
|
||||
|
||||
def test_date_modified_invalid(photosdb):
|
||||
""" Test date modified is invalid """
|
||||
"""Test date modified is invalid"""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
# UUID_DICT["date_invalid"] has an invalid modified date that's
|
||||
@@ -1028,7 +1028,7 @@ def test_date_modified_invalid(photosdb):
|
||||
|
||||
|
||||
def test_uti(photosdb):
|
||||
""" test uti """
|
||||
"""test uti"""
|
||||
|
||||
for uuid, uti in UTI_DICT.items():
|
||||
photo = photosdb.get_photo(uuid)
|
||||
@@ -1037,7 +1037,7 @@ def test_uti(photosdb):
|
||||
|
||||
|
||||
def test_raw(photosdb):
|
||||
""" Test various raw properties """
|
||||
"""Test various raw properties"""
|
||||
|
||||
for uuid, rawinfo in RAW_DICT.items():
|
||||
photo = photosdb.get_photo(uuid)
|
||||
@@ -1050,7 +1050,7 @@ def test_raw(photosdb):
|
||||
|
||||
|
||||
def test_is_reference(photosdb):
|
||||
""" test isreference """
|
||||
"""test isreference"""
|
||||
|
||||
photo = photosdb.get_photo(UUID_IS_REFERENCE)
|
||||
assert photo.isreference
|
||||
@@ -1059,7 +1059,7 @@ def test_is_reference(photosdb):
|
||||
|
||||
|
||||
def test_adjustments(photosdb):
|
||||
""" test adjustments/AdjustmentsInfo """
|
||||
"""test adjustments/AdjustmentsInfo"""
|
||||
from osxphotos.adjustmentsinfo import AdjustmentsInfo
|
||||
|
||||
photo = photosdb.get_photo(UUID_DICT["adjustments_info"])
|
||||
@@ -1121,7 +1121,7 @@ def test_adjustments(photosdb):
|
||||
|
||||
|
||||
def test_no_adjustments(photosdb):
|
||||
""" test adjustments when photo has no adjusments"""
|
||||
"""test adjustments when photo has no adjusments"""
|
||||
|
||||
photo = photosdb.get_photo(UUID_DICT["no_adjustments"])
|
||||
assert photo.adjustments is None
|
||||
|
||||
@@ -1066,7 +1066,7 @@ def test_photosdb_repr():
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photosdb2 = eval(repr(photosdb))
|
||||
|
||||
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name"]
|
||||
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name", "_db_connection"]
|
||||
assert {k: v for k, v in photosdb.__dict__.items() if k not in ignore_keys} == {
|
||||
k: v for k, v in photosdb2.__dict__.items() if k not in ignore_keys
|
||||
}
|
||||
|
||||
@@ -1483,6 +1483,50 @@ def test_export_preview_if_missing():
|
||||
assert sorted(files) == sorted(expected_files)
|
||||
|
||||
|
||||
def test_export_preview_overwrite():
|
||||
"""test export with --preview and --overwrite (#526)"""
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
|
||||
import osxphotos
|
||||
from osxphotos.cli import export
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(
|
||||
export,
|
||||
[
|
||||
os.path.join(cwd, CLI_PHOTOS_DB),
|
||||
".",
|
||||
"-V",
|
||||
"--preview",
|
||||
"--uuid",
|
||||
CLI_EXPORT_UUID,
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
|
||||
# export again
|
||||
result = runner.invoke(
|
||||
export,
|
||||
[
|
||||
os.path.join(cwd, CLI_PHOTOS_DB),
|
||||
".",
|
||||
"-V",
|
||||
"--preview",
|
||||
"--uuid",
|
||||
CLI_EXPORT_UUID,
|
||||
"--overwrite",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
files = glob.glob("*")
|
||||
assert len(files) == 2 # preview + original
|
||||
|
||||
|
||||
def test_export_as_hardlink():
|
||||
import glob
|
||||
import os
|
||||
@@ -6928,6 +6972,37 @@ def test_query_regex_4():
|
||||
assert len(json_got) == 2
|
||||
|
||||
|
||||
def test_query_regex_multiple():
|
||||
"""test query multiple --regex values (#525)"""
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
|
||||
import osxphotos
|
||||
from osxphotos.cli import query
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
result = runner.invoke(
|
||||
query,
|
||||
[
|
||||
"--json",
|
||||
"--db",
|
||||
os.path.join(cwd, PHOTOS_DB_15_7),
|
||||
"--regex",
|
||||
"I found",
|
||||
"{title}",
|
||||
"--regex",
|
||||
"carry",
|
||||
"{title}",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
json_got = json.loads(result.output)
|
||||
|
||||
assert len(json_got) == 2
|
||||
|
||||
|
||||
def test_query_function():
|
||||
"""test query --query-function"""
|
||||
import json
|
||||
|
||||
43
tests/test_cloud_owner_catalina.py
Normal file
43
tests/test_cloud_owner_catalina.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# Test cloud photos and album owner
|
||||
|
||||
import pytest
|
||||
|
||||
import osxphotos
|
||||
|
||||
PHOTOS_DB_CLOUD = "./tests/Test-Cloud-10.15.6.photoslibrary/"
|
||||
PHOTOS_DB_NOT_CLOUD = "./tests/Test-10.15.6.photoslibrary/"
|
||||
|
||||
UUID_DICT = {
|
||||
"not_cloudasset": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
|
||||
"owner": "7572C53E-1D6A-410C-A2B1-18CCA3B5AD9F",
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def photosdb_cloud():
|
||||
return osxphotos.PhotosDB(dbfile=PHOTOS_DB_CLOUD)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def photosdb_nocloud():
|
||||
return osxphotos.PhotosDB(dbfile=PHOTOS_DB_NOT_CLOUD)
|
||||
|
||||
|
||||
def test_album_owner_cloud(photosdb_cloud):
|
||||
album = [a for a in photosdb_cloud.album_info_shared if a.title == "osxphotos"][0]
|
||||
assert album.owner == "Rhet Turnbull"
|
||||
|
||||
|
||||
def test_album_owner_not_cloud(photosdb_nocloud):
|
||||
album = [a for a in photosdb_nocloud.album_info if a.title == "Test Album"][0]
|
||||
assert album.owner is None
|
||||
|
||||
|
||||
def test_photo_owner_cloud(photosdb_cloud):
|
||||
photo = photosdb_cloud.get_photo(UUID_DICT["owner"])
|
||||
assert photo.owner == "Rhet Turnbull"
|
||||
|
||||
|
||||
def test_photo_owner_nocloud(photosdb_nocloud):
|
||||
photo = photosdb_nocloud.get_photo(UUID_DICT["not_cloudasset"])
|
||||
assert photo.owner is None
|
||||
@@ -13,7 +13,7 @@ COMMENT_UUID_DICT = {
|
||||
"4AD7C8EF-2991-4519-9D3A-7F44A6F031BE": [
|
||||
CommentInfo(
|
||||
datetime=datetime.datetime(2020, 9, 18, 10, 28, 41, 552000),
|
||||
user=None,
|
||||
user="Rhet Turnbull",
|
||||
ismine=False,
|
||||
text="Nice photo!",
|
||||
),
|
||||
@@ -39,7 +39,7 @@ LIKE_UUID_DICT = {
|
||||
"4AD7C8EF-2991-4519-9D3A-7F44A6F031BE": [
|
||||
LikeInfo(
|
||||
datetime=datetime.datetime(2020, 9, 18, 10, 28, 43, 335000),
|
||||
user=None,
|
||||
user="Rhet Turnbull",
|
||||
ismine=False,
|
||||
)
|
||||
],
|
||||
@@ -47,7 +47,7 @@ LIKE_UUID_DICT = {
|
||||
"65BADBD7-A50C-4956-96BA-1BB61155DA17": [
|
||||
LikeInfo(
|
||||
datetime=datetime.datetime(2020, 9, 18, 10, 28, 52, 570000),
|
||||
user=None,
|
||||
user="Rhet Turnbull",
|
||||
ismine=False,
|
||||
)
|
||||
],
|
||||
@@ -65,7 +65,7 @@ COMMENT_UUID_ASDICT = {
|
||||
LIKE_UUID_ASDICT = {
|
||||
"65BADBD7-A50C-4956-96BA-1BB61155DA17": {
|
||||
"datetime": datetime.datetime(2020, 9, 18, 10, 28, 52, 570000),
|
||||
"user": None,
|
||||
"user": "Rhet Turnbull",
|
||||
"ismine": False,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ pytestmark = pytest.mark.skipif(
|
||||
)
|
||||
|
||||
UUID_DICT = {
|
||||
"has_adjustments": "C925CFDC-FF2B-4E71-AC9D-C669B6453A8B",
|
||||
"no_adjustments": "16A6AF6B-D8FC-4256-AE33-889733E3EEAB",
|
||||
"live": "8EC216A2-0032-4934-BD3F-04C6259B3304",
|
||||
"has_adjustments": "C925CFDC-FF2B-4E71-AC9D-C669B6453A8B", # IMG_1929.JPG
|
||||
"no_adjustments": "16A6AF6B-D8FC-4256-AE33-889733E3EEAB", # IMG_9847.JPG
|
||||
"live": "8EC216A2-0032-4934-BD3F-04C6259B3304", # IMG_3259.HEIC
|
||||
}
|
||||
|
||||
UUID_BURSTS = {
|
||||
|
||||
@@ -266,7 +266,7 @@ def test_attributes(photosdb):
|
||||
|
||||
|
||||
def test_attributes_2(photosdb):
|
||||
""" Test attributes including height, width, etc """
|
||||
"""Test attributes including height, width, etc"""
|
||||
import datetime
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
|
||||
@@ -338,7 +338,7 @@ def test_not_hidden(photosdb):
|
||||
|
||||
|
||||
def test_visible(photosdb):
|
||||
""" test visible """
|
||||
"""test visible"""
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["not_hidden"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
@@ -346,7 +346,7 @@ def test_visible(photosdb):
|
||||
|
||||
|
||||
def test_not_burst(photosdb):
|
||||
""" test not burst """
|
||||
"""test not burst"""
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["not_hidden"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
@@ -448,13 +448,13 @@ def test_count(photosdb):
|
||||
|
||||
|
||||
def test_photos_intrash_1(photosdb):
|
||||
""" test PhotosDB.photos(intrash=True) """
|
||||
"""test PhotosDB.photos(intrash=True)"""
|
||||
photos = photosdb.photos(intrash=True)
|
||||
assert len(photos) == PHOTOS_IN_TRASH_LEN
|
||||
|
||||
|
||||
def test_photos_intrash_2(photosdb):
|
||||
""" test PhotosDB.photos(intrash=True) """
|
||||
"""test PhotosDB.photos(intrash=True)"""
|
||||
photos = photosdb.photos(intrash=True)
|
||||
for p in photos:
|
||||
assert p.intrash
|
||||
@@ -462,7 +462,7 @@ def test_photos_intrash_2(photosdb):
|
||||
|
||||
|
||||
def test_photos_not_intrash(photosdb):
|
||||
""" test PhotosDB.photos(intrash=False) """
|
||||
"""test PhotosDB.photos(intrash=False)"""
|
||||
photos = photosdb.photos(intrash=False)
|
||||
for p in photos:
|
||||
assert not p.intrash
|
||||
@@ -470,19 +470,19 @@ def test_photos_not_intrash(photosdb):
|
||||
|
||||
|
||||
def test_photoinfo_intrash_1(photosdb):
|
||||
""" Test PhotoInfo.intrash """
|
||||
"""Test PhotoInfo.intrash"""
|
||||
p = photosdb.photos(uuid=[UUID_DICT["intrash"]], intrash=True)[0]
|
||||
assert p.intrash
|
||||
|
||||
|
||||
def test_photoinfo_intrash_2(photosdb):
|
||||
""" Test PhotoInfo.intrash and intrash=default"""
|
||||
"""Test PhotoInfo.intrash and intrash=default"""
|
||||
p = photosdb.photos(uuid=[UUID_DICT["intrash"]])
|
||||
assert not p
|
||||
|
||||
|
||||
def test_photoinfo_not_intrash(photosdb):
|
||||
""" Test PhotoInfo.intrash """
|
||||
"""Test PhotoInfo.intrash"""
|
||||
p = photosdb.photos(uuid=[UUID_DICT["not_intrash"]])[0]
|
||||
assert not p.intrash
|
||||
|
||||
@@ -517,7 +517,7 @@ def test_photosdb_repr():
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photosdb2 = eval(repr(photosdb))
|
||||
|
||||
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name"]
|
||||
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name", "_db_connection"]
|
||||
assert {k: v for k, v in photosdb.__dict__.items() if k not in ignore_keys} == {
|
||||
k: v for k, v in photosdb2.__dict__.items() if k not in ignore_keys
|
||||
}
|
||||
@@ -562,7 +562,7 @@ def test_multi_person(photosdb):
|
||||
|
||||
|
||||
def test_date_invalid(photosdb):
|
||||
""" Test date is invalid """
|
||||
"""Test date is invalid"""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["date_invalid"]])
|
||||
@@ -574,7 +574,7 @@ def test_date_invalid(photosdb):
|
||||
|
||||
|
||||
def test_date_modified_invalid(photosdb):
|
||||
""" Test date modified is invalid """
|
||||
"""Test date modified is invalid"""
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["date_invalid"]])
|
||||
assert len(photos) == 1
|
||||
@@ -583,7 +583,7 @@ def test_date_modified_invalid(photosdb):
|
||||
|
||||
|
||||
def test_date_modified(photosdb):
|
||||
""" Test date modified for photo that has been edited """
|
||||
"""Test date modified for photo that has been edited"""
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
|
||||
p = photos[0]
|
||||
@@ -600,7 +600,7 @@ def test_date_modified(photosdb):
|
||||
|
||||
|
||||
def test_date_modified_none(photosdb):
|
||||
""" Test date modified for a photo that hasn't been edited """
|
||||
"""Test date modified for a photo that hasn't been edited"""
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_adjustments"]])
|
||||
p = photos[0]
|
||||
@@ -638,7 +638,7 @@ def test_raw(photosdb):
|
||||
|
||||
|
||||
def test_raw():
|
||||
""" Test various raw properties """
|
||||
"""Test various raw properties"""
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
@@ -654,7 +654,7 @@ def test_raw():
|
||||
|
||||
|
||||
def test_is_reference(photosdb):
|
||||
""" test isreference """
|
||||
"""test isreference"""
|
||||
|
||||
photo = photosdb.get_photo(UUID_IS_REFERENCE)
|
||||
assert photo.isreference
|
||||
@@ -663,7 +663,7 @@ def test_is_reference(photosdb):
|
||||
|
||||
|
||||
def test_adjustments(photosdb):
|
||||
""" test adjustments/AdjustmentsInfo (not implemented for 10.14) """
|
||||
"""test adjustments/AdjustmentsInfo (not implemented for 10.14)"""
|
||||
from osxphotos.adjustmentsinfo import AdjustmentsInfo
|
||||
|
||||
photo = photosdb.get_photo(UUID_DICT["has_adjustments"])
|
||||
@@ -671,7 +671,7 @@ def test_adjustments(photosdb):
|
||||
|
||||
|
||||
def test_no_adjustments(photosdb):
|
||||
""" test adjustments when photo has no adjusments"""
|
||||
"""test adjustments when photo has no adjusments"""
|
||||
|
||||
photo = photosdb.get_photo(UUID_DICT["no_adjustments"])
|
||||
assert photo.adjustments is None
|
||||
|
||||
@@ -1033,7 +1033,7 @@ def test_photosdb_repr():
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photosdb2 = eval(repr(photosdb))
|
||||
|
||||
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name"]
|
||||
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name", "_db_connection"]
|
||||
assert {k: v for k, v in photosdb.__dict__.items() if k not in ignore_keys} == {
|
||||
k: v for k, v in photosdb2.__dict__.items() if k not in ignore_keys
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ TEMPLATE_VALUES_DETECTED_TEXT = {
|
||||
|
||||
COMMENT_UUID_DICT = {
|
||||
"4AD7C8EF-2991-4519-9D3A-7F44A6F031BE": [
|
||||
"None: Nice photo!",
|
||||
"Rhet Turnbull: Nice photo!",
|
||||
"None: Wish I was back here!",
|
||||
],
|
||||
"CCBE0EB9-AE9F-4479-BFFD-107042C75227": ["_"],
|
||||
|
||||
4
tox.ini
4
tox.ini
@@ -2,9 +2,11 @@
|
||||
# in multiple virtualenvs. This configuration file will run the
|
||||
# test suite on all supported python versions. To use it, "pip install tox"
|
||||
# and then run "tox" from this directory.
|
||||
# To use with pyenv, install pip install tox-pyenv then set local versions with pyenv local, e.g.:
|
||||
# pyenv local osxphotos-3.9.5 3.10.0rc1 3.8.12 3.7.11
|
||||
|
||||
[tox]
|
||||
envlist = py37, py38
|
||||
envlist = py37, py38, py39, py310
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
|
||||
Reference in New Issue
Block a user