Compare commits

...

25 Commits

Author SHA1 Message Date
Rhet Turnbull
4ec9f6d3e6 Implemented #689 2022-05-17 20:52:59 -07:00
Rhet Turnbull
5a9722b37c Unhid exportdb command 2022-05-16 10:15:57 -07:00
Rhet Turnbull
72af96b48e Added example 2022-05-15 14:50:07 -07:00
Rhet Turnbull
ba4d878b1f Updated CHANGELOG.md [skip ci] 2022-05-15 12:52:39 -07:00
Rhet Turnbull
1fb8fa7da2 Fixed typos 2022-05-15 10:38:01 -07:00
Rhet Turnbull
1173b6c0f2 Added JSON, SQLite report formats, added command 2022-05-15 09:48:55 -07:00
Rhet Turnbull
470839ba0d Feature report writer #309 (#690)
* Initial implementation of ReportWriter for #309

* Initial implementation of ReportWriterJSON

* Added sqlite report format

* Added auto-flush to report writer, fixed exportdb --info to output json

* Added exportdb --report
2022-05-15 09:30:17 -07:00
Rhet Turnbull
391815dd94 --report can now accept a template, #339 2022-05-14 09:04:04 -07:00
Rhet Turnbull
b4dc7cfcf6 Fixed run command to allow passing args to the called python script 2022-05-13 05:50:16 -07:00
Rhet Turnbull
a89c66b3f7 Fixed test to run on MacOS > Catalina 2022-05-12 21:48:53 -07:00
Rhet Turnbull
5cd74b5f23 version bump 2022-05-08 20:37:37 -07:00
Rhet Turnbull
abbb200838 Fixed path_derivatives for shared photos, #687 2022-05-08 18:30:13 -07:00
Rhet Turnbull
e7eefce5c5 Added --no-keyword, #637 2022-05-07 14:24:06 -07:00
Rhet Turnbull
2ed6e11426 Fixed typo in docs 2022-05-07 10:00:51 -07:00
Rhet Turnbull
4096054abb Updated CHANGELOG.md [skip ci] 2022-05-07 09:15:32 -07:00
Rhet Turnbull
5ab5c53b26 Added --limit, version bump 2022-05-07 09:11:21 -07:00
Rhet Turnbull
35a4777ae4 Added --limit, #592 (#685) 2022-05-07 08:59:01 -07:00
Rhet Turnbull
9fddbefa66 Updated CHANGELOG.md [skip ci] 2022-05-06 21:33:08 -07:00
Rhet Turnbull
ff2e810d49 Version bump, updated docs 2022-05-06 21:26:27 -07:00
Rhet Turnbull
026e90a8ed Feature metadata changed 621 (#684)
* Added metadata_changed to ExportResults, #621

* Updated docs
2022-05-06 21:05:59 -07:00
Rhet Turnbull
95103f7c8d Updated CHANGELOG.md [skip ci] 2022-05-05 17:40:45 -07:00
Rhet Turnbull
57deb23988 Added --added-after, --added-before, --added-in-last, #439 2022-05-05 17:38:59 -07:00
Rhet Turnbull
4533057142 Added --added-after, --added-before, --added-in-last, #439 (#683) 2022-05-05 17:06:55 -07:00
Rhet Turnbull
3ed658a7d0 Updated to pytimeparse2, added tests for custom Click param types 2022-05-05 07:00:52 -07:00
Rhet Turnbull
fa991b8b48 Updated CHANGELOG.md [skip ci] 2022-05-03 22:28:29 -07:00
67 changed files with 4445 additions and 2062 deletions

View File

@@ -1943,7 +1943,7 @@ cog.out(get_template_field_table())
|{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.48.2'|
|{osxphotos_version}|The osxphotos version, e.g. '0.49.1'|
|{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|
@@ -2103,6 +2103,7 @@ Attributes:
`ExportResults` has the following properties:
* datetime: date/time of export in ISO 8601 format
* exported: list of all exported files (A single call to export could export more than one file, e.g. original file, preview, live video, raw, etc.)
* new: list of new files exported when used with update=True
* updated: list of updated files when used with update=True

View File

@@ -4,6 +4,64 @@ 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.49.0](https://github.com/RhetTbull/osxphotos/compare/v0.48.8...v0.49.0)
> 15 May 2022
- Feature report writer #309 [`#690`](https://github.com/RhetTbull/osxphotos/pull/690)
- Added JSON, SQLite report formats, added command [`1173b6c`](https://github.com/RhetTbull/osxphotos/commit/1173b6c0f294e12269d4015d2823b590df6f9696)
- --report can now accept a template, #339 [`391815d`](https://github.com/RhetTbull/osxphotos/commit/391815dd9401fb0b47c0f935cf844bbb85e5ae55)
- Fixed run command to allow passing args to the called python script [`b4dc7cf`](https://github.com/RhetTbull/osxphotos/commit/b4dc7cfcf6af1a4b88fc826b99a900318f5b1c84)
- Fixed test to run on MacOS > Catalina [`a89c66b`](https://github.com/RhetTbull/osxphotos/commit/a89c66b3f773b31199febc3daf1d2441f3fdfa43)
#### [v0.48.8](https://github.com/RhetTbull/osxphotos/compare/v0.48.7...v0.48.8)
> 8 May 2022
- version bump [`5cd74b5`](https://github.com/RhetTbull/osxphotos/commit/5cd74b5f2373b48c261bea3c6378945ee216531a)
#### [v0.48.7](https://github.com/RhetTbull/osxphotos/compare/v0.48.6...v0.48.7)
> 8 May 2022
- Fixed path_derivatives for shared photos, #687 [`abbb200`](https://github.com/RhetTbull/osxphotos/commit/abbb200838535e03cf4adcb339ad3a2c18f32dc3)
- Added --no-keyword, #637 [`e7eefce`](https://github.com/RhetTbull/osxphotos/commit/e7eefce5c51e1e17e64ac44b6c0a9944873f68a3)
- Fixed typo in docs [`2ed6e11`](https://github.com/RhetTbull/osxphotos/commit/2ed6e1142644e686b6b645d3bedab3648e13f9df)
#### [v0.48.6](https://github.com/RhetTbull/osxphotos/compare/v0.48.5...v0.48.6)
> 7 May 2022
- Added --limit, version bump [`5ab5c53`](https://github.com/RhetTbull/osxphotos/commit/5ab5c53b26207eb326191c110ac544592c980c32)
#### [v0.48.5](https://github.com/RhetTbull/osxphotos/compare/v0.48.4...v0.48.5)
> 7 May 2022
- Added --limit, #592 [`#685`](https://github.com/RhetTbull/osxphotos/pull/685)
#### [v0.48.4](https://github.com/RhetTbull/osxphotos/compare/v0.48.3...v0.48.4)
> 6 May 2022
- Feature metadata changed 621 [`#684`](https://github.com/RhetTbull/osxphotos/pull/684)
- Version bump, updated docs [`ff2e810`](https://github.com/RhetTbull/osxphotos/commit/ff2e810d49f9acffb81f3b7aea6271191d6762f9)
#### [v0.48.3](https://github.com/RhetTbull/osxphotos/compare/v0.48.2...v0.48.3)
> 5 May 2022
- Added --added-after, --added-before, --added-in-last, #439 [`#683`](https://github.com/RhetTbull/osxphotos/pull/683)
- Updated to pytimeparse2, added tests for custom Click param types [`3ed658a`](https://github.com/RhetTbull/osxphotos/commit/3ed658a7d018b5ac61c1054e20a59576e8613609)
#### [v0.48.2](https://github.com/RhetTbull/osxphotos/compare/v0.48.1...v0.48.2)
> 3 May 2022
- Added moment_info, #71 [`9bc5890`](https://github.com/RhetTbull/osxphotos/commit/9bc5890589b7ff45d7b1e8bca4f958a6b4dc5fad)
- Added --force to timewarp to bypass confirmation [`f42bee8`](https://github.com/RhetTbull/osxphotos/commit/f42bee84c08cf8c6883058a8c2d45dfe9c3e2357)
- Added confirmation for timewarp, #677 [`ac67ef2`](https://github.com/RhetTbull/osxphotos/commit/ac67ef23846fb2a35211be8b63f5201a5567c68e)
#### [v0.48.1](https://github.com/RhetTbull/osxphotos/compare/v0.48.0...v0.48.1)
> 1 May 2022

View File

@@ -378,7 +378,7 @@ Photos tracks a tremendous amount of metadata associated with photos in the libr
`osxphotos export /path/to/export --exiftool`
This will write basic metadata such as keywords, persons, and GPS location to the exported files. osxphotos includes several additional options that can be used in conjunction with `--exiftool` to modify the metadata that is written by `exiftool`. For example, you can use the `--keyword-template` option to specify custom keywords (again, via the osxphotos template system). For example, to use the folder and album a photo is in to create hierarchal keywords in the format used by Lightroom Classic:
This will write basic metadata such as keywords, persons, and GPS location to the exported files. osxphotos includes several additional options that can be used in conjunction with `--exiftool` to modify the metadata that is written by `exiftool`. For example, you can use the `--keyword-template` option to specify custom keywords (again, via the osxphotos template system). For example, to use the folder and album a photo is in to create hierarchical keywords in the format used by Lightroom Classic:
osxphotos export /path/to/export --exiftool --keyword-template "{folder_album(>)}"
│ │
@@ -390,7 +390,7 @@ This will write basic metadata such as keywords, persons, and GPS location to th
for joining the folders and albums. For example,
if photo is in Folder1/Folder2/Album, (>) produces
"Folder1>Folder2>Album" which some programs, such as
Lightroom Classic, treat as hierarchal keywords
Lightroom Classic, treat as hierarchical keywords
The above command will write all the regular metadata that `--exiftool` normally writes to the file upon export but will also add an additional keyword in the exported metadata in the form "Folder1>Folder2>Album". If you did not include the `(>)` in the template string (e.g. `{folder_album}`), folder_album would render in form "Folder1/Folder2/Album".
@@ -472,6 +472,10 @@ You can also export photos in a certain date range:
`osxphotos export /path/to/export --from-date "2020-01-01" --to-date "2020-02-28"`
or photos added to the library in the last week:
`osxphotos export /path/to/export --added-in-last "1 week"`
#### Converting images to JPEG on export
Photos can store images in many different formats. osxphotos can convert non-JPEG images (for example, RAW photos) to JPEG on export using the `--convert-to-jpeg` option. You can specify the JPEG quality (0: worst, 1.0: best) using `--jpeg-quality`. For example:
@@ -645,6 +649,7 @@ Options:
--keyword KEYWORD Search for photos with keyword KEYWORD. If
more than one keyword, treated as "OR", e.g.
find photos matching any keyword
--no-keyword Search for photos with no keyword.
--person PERSON Search for photos with person PERSON. If more
than one person, treated as "OR", e.g. find
photos matching any person
@@ -742,6 +747,26 @@ Options:
--year 2022 to find all photos from the year
2022. May be repeated to search multiple
years.
--added-before DATE Search for items added to the library before a
specific date/time, e.g. --added-before e.g.
2000-01-12T12:00:00,
2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO
8601 with/without timezone).
--added-after DATE Search for items added to the libray after a
specific date/time, e.g. --added-after e.g.
2000-01-12T12:00:00,
2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO
8601 with/without timezone).
--added-in-last TIME_DELTA Search for items added to the library in the
last TIME_DELTA, where TIME_DELTA is a string
like '12 hrs', '1 day', '1d', '1 week',
'2weeks', '1 month', '1 year'. for example,
`--added-in-last 7d` and `--added-in-last '1
week'` are equivalent. months are assumed to
be 30 days and years are assumed to be 365
days. Common English abbreviations are
accepted, e.g. d, day, days or m, min,
minutes.
--has-comment Search for photos that have comments.
--no-comment Search for photos with no comments.
--has-likes Search for photos that have likes.
@@ -858,6 +883,9 @@ Options:
exported files, even if missing from the
export folder and only exports new files that
haven't previously been exported.
--limit LIMIT Export at most LIMIT photos. Useful for
testing. May be used with --update to export
incrementally.
--dry-run Dry run (test) the export but don't actually
export any files; most useful with --verbose.
--export-as-hardlink Hardlink files instead of copying them. Cannot
@@ -1139,8 +1167,19 @@ Options:
iTerm2 (use with Terminal.app). This is faster
and more reliable than the default AppleScript
interface.
--report REPORT_FILE Write a CSV formatted report of all files that
were exported.
--report REPORT_FILE Write a report of all files that were
exported. The extension of the report filename
will be used to determine the format. Valid
extensions are: .csv (CSV file), .json (JSON),
.db and .sqlite (SQLite database). REPORT_FILE
may be a template string (see Templating
System), for example, --report
'export_{today.date}.csv' will write a CSV
report file named with today's date. See also
--append.
--append If used with --report, add data to existing
report file instead of overwriting it. See
also --report.
--cleanup Cleanup export directory by deleting any files
which were not included in this export set.
For example, photos which had previously been
@@ -1807,7 +1846,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.48.2'
{osxphotos_version} The osxphotos version, e.g. '0.49.1'
{osxphotos_cmd_line} The full command line used to run osxphotos
The following substitutions may result in multiple values. Thus if specified
@@ -2957,6 +2996,9 @@ Returns a [ScoreInfo](#scoreinfo) data class object which provides access to the
Returns list of PhotoInfo objects for *possible* duplicates or empty list if no matching duplicates. Photos are considered possible duplicates if the photo's original file size, date created, height, and width match another those of another photo. This does not do a byte-for-byte comparison or compute a hash which makes it fast and allows for identification of possible duplicates even if originals are not downloaded from iCloud. The signature-based approach should be robust enough to match duplicates created either through the "duplicate photo" menu item or imported twice into the library but you should not rely on this 100% for identification of all duplicates.
#### `hexdigest`
Returns a unique digest of the photo's properties and metadata; useful for detecting changes in any property/metadata of the photo.
#### `json()`
Returns a JSON representation of all photo info.
@@ -3970,7 +4012,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.48.2'|
|{osxphotos_version}|The osxphotos version, e.g. '0.49.1'|
|{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|
@@ -4130,6 +4172,7 @@ Attributes:
`ExportResults` has the following properties:
* datetime: date/time of export in ISO 8601 format
* exported: list of all exported files (A single call to export could export more than one file, e.g. original file, preview, live video, raw, etc.)
* new: list of new files exported when used with update=True
* updated: list of updated files when used with update=True
@@ -4150,6 +4193,7 @@ Attributes:
* exiftool_error: list of errors generated by exiftool during export
* xattr_written: list of files with extended attributes written during export
* xattr_skipped: list of files where extended attributes were skipped when update=True
* metadata_changed: list of files where metadata changed since last export
* deleted_files: reserved for use by osxphotos CLI
* deleted_directories: reserved for use by osxphotos CLI
* exported_album: reserved for use by osxphotos CLI

View File

@@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 80f83a536e4014ef4d71db9e7d4e82e8
config: a77e6404d00931c687c8320621f91963
tags: 645f666f9bcd5a90fca523b33c5a78b7

View File

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

View File

@@ -1,43 +1,206 @@
<!doctype html>
<html class="no-js">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<!DOCTYPE html>
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.exiftool - osxphotos 0.49.0 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos.exiftool &#8212; osxphotos 0.47.9 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>
<script src="../../_static/jquery.js"></script>
<script src="../../_static/underscore.js"></script>
<script src="../../_static/doctools.js"></script>
<link rel="index" title="Index" href="../../genindex.html" />
<link rel="search" title="Search" href="../../search.html" />
<link rel="stylesheet" href="../../_static/custom.css" type="text/css" />
<style>
body {
--color-code-background: #f8f8f8;
--color-code-foreground: black;
}
@media not print {
body[data-theme="dark"] {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
}
@media (prefers-color-scheme: dark) {
body:not([data-theme="light"]) {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
}
}
}
</style></head>
<body>
<script>
document.body.dataset.theme = localStorage.getItem("theme") || "auto";
</script>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-toc" viewBox="0 0 24 24">
<title>Contents</title>
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 1024 1024">
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z"/>
</svg>
</symbol>
<symbol id="svg-menu" viewBox="0 0 24 24">
<title>Menu</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-menu">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</symbol>
<symbol id="svg-arrow-right" viewBox="0 0 24 24">
<title>Expand</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-chevron-right">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</symbol>
<symbol id="svg-sun" viewBox="0 0 24 24">
<title>Light mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather-sun">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
<symbol id="svg-moon" viewBox="0 0 24 24">
<title>Dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-moon">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />
</svg>
</symbol>
<symbol id="svg-sun-half" viewBox="0 0 24 24">
<title>Auto light/dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-shadow">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<circle cx="12" cy="12" r="9" />
<path d="M13 12h5" />
<path d="M13 15h4" />
<path d="M13 18h1" />
<path d="M13 9h4" />
<path d="M13 6h1" />
</svg>
</symbol>
</svg>
<input type="checkbox" class="sidebar-toggle" name="__navigation" id="__navigation">
<input type="checkbox" class="sidebar-toggle" name="__toc" id="__toc">
<label class="overlay sidebar-overlay" for="__navigation">
<div class="visually-hidden">Hide navigation sidebar</div>
</label>
<label class="overlay toc-overlay" for="__toc">
<div class="visually-hidden">Hide table of contents sidebar</div>
</label>
<div class="page">
<header class="mobile-header">
<div class="header-left">
<label class="nav-overlay-icon" for="__navigation">
<div class="visually-hidden">Toggle site navigation sidebar</div>
<i class="icon"><svg><use href="#svg-menu"></use></svg></i>
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.49.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
<button class="theme-toggle">
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-header-icon no-toc" for="__toc">
<div class="visually-hidden">Toggle table of contents sidebar</div>
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
</label>
</div>
</header>
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
</head><body>
<span class="sidebar-brand-text">osxphotos 0.49.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
<input type="hidden" name="check_keywords" value="yes">
<input type="hidden" name="area" value="default">
</form>
<div id="searchbox"></div><div class="sidebar-scroll"><div class="sidebar-tree">
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../overview.html">OSXPhotos</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../tutorial.html">OSXPhotos Tutorial</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../cli.html">OSXPhotos Command Line Interface (CLI)</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../template_help.html">OSXPhotos Template System</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../package_overview.html">OSXPhotos Python Package Overview</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../reference.html">OSXPhotos python API</a></li>
</ul>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
</div>
</div>
<div class="body" role="main">
<h1>Source code for osxphotos.exiftool</h1><div class="highlight"><pre>
<span></span><span class="sd">&quot;&quot;&quot; Yet another simple exiftool wrapper </span>
</div>
</div>
</aside>
<div class="main">
<div class="content">
<div class="article-container">
<a href="#" class="back-to-top muted-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z"></path>
</svg>
<span>Back to top</span>
</a>
<div class="content-icon-container"><div class="theme-toggle-container theme-toggle-content">
<button class="theme-toggle">
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-content-icon no-toc" for="__toc">
<div class="visually-hidden">Toggle table of contents sidebar</div>
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
</label>
</div>
<article role="main">
<h1>Source code for osxphotos.exiftool</h1><div class="highlight"><pre>
<span></span><span class="sd">""" Yet another simple exiftool wrapper </span>
<span class="sd"> I rolled my own for following reasons: </span>
<span class="sd"> 1. I wanted something under MIT license (best alternative was licensed under GPL/BSD)</span>
<span class="sd"> 2. I wanted singleton behavior so only a single exiftool process was ever running</span>
<span class="sd"> 3. When used as a context manager, I wanted the operations to batch until exiting the context (improved performance)</span>
<span class="sd"> If these aren&#39;t important to you, I highly recommend you use Sven Marnach&#39;s excellent </span>
<span class="sd"> pyexiftool: https://github.com/smarnach/pyexiftool which provides more functionality &quot;&quot;&quot;</span>
<span class="sd"> If these aren't important to you, I highly recommend you use Sven Marnach's excellent </span>
<span class="sd"> pyexiftool: https://github.com/smarnach/pyexiftool which provides more functionality """</span>
<span class="kn">import</span> <span class="nn">atexit</span>
<span class="kn">import</span> <span class="nn">html</span>
@@ -52,103 +215,102 @@
<span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</span> <span class="c1"># pylint: disable=syntax-error</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span>
<span class="s2">&quot;escape_str&quot;</span><span class="p">,</span>
<span class="s2">&quot;exiftool_can_write&quot;</span><span class="p">,</span>
<span class="s2">&quot;ExifTool&quot;</span><span class="p">,</span>
<span class="s2">&quot;ExifToolCaching&quot;</span><span class="p">,</span>
<span class="s2">&quot;get_exiftool_path&quot;</span><span class="p">,</span>
<span class="s2">&quot;terminate_exiftool&quot;</span><span class="p">,</span>
<span class="s2">&quot;unescape_str&quot;</span><span class="p">,</span>
<span class="s2">"escape_str"</span><span class="p">,</span>
<span class="s2">"exiftool_can_write"</span><span class="p">,</span>
<span class="s2">"ExifTool"</span><span class="p">,</span>
<span class="s2">"ExifToolCaching"</span><span class="p">,</span>
<span class="s2">"get_exiftool_path"</span><span class="p">,</span>
<span class="s2">"terminate_exiftool"</span><span class="p">,</span>
<span class="s2">"unescape_str"</span><span class="p">,</span>
<span class="p">]</span>
<span class="c1"># exiftool -stay_open commands outputs this EOF marker after command is run</span>
<span class="n">EXIFTOOL_STAYOPEN_EOF</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="si">{ready}</span><span class="s2">&quot;</span>
<span class="n">EXIFTOOL_STAYOPEN_EOF</span> <span class="o">=</span> <span class="s2">"</span><span class="si">{ready}</span><span class="s2">"</span>
<span class="n">EXIFTOOL_STAYOPEN_EOF_LEN</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">EXIFTOOL_STAYOPEN_EOF</span><span class="p">)</span>
<span class="c1"># list of exiftool processes to cleanup when exiting or when terminate is called</span>
<span class="n">EXIFTOOL_PROCESSES</span> <span class="o">=</span> <span class="p">[]</span>
<span class="c1"># exiftool supported file types, created by utils/exiftool_supported_types.py</span>
<span class="n">EXIFTOOL_FILETYPES_JSON</span> <span class="o">=</span> <span class="s2">&quot;exiftool_filetypes.json&quot;</span>
<span class="k">with</span> <span class="p">(</span><span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="n">EXIFTOOL_FILETYPES_JSON</span><span class="p">)</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s2">&quot;r&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">EXIFTOOL_FILETYPES_JSON</span> <span class="o">=</span> <span class="s2">"exiftool_filetypes.json"</span>
<span class="k">with</span> <span class="p">(</span><span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="n">EXIFTOOL_FILETYPES_JSON</span><span class="p">)</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s2">"r"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">EXIFTOOL_SUPPORTED_FILETYPES</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">exiftool_can_write</span><span class="p">(</span><span class="n">suffix</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Return True if exiftool supports writing to a file with the given suffix, otherwise False&quot;&quot;&quot;</span>
<span class="sd">"""Return True if exiftool supports writing to a file with the given suffix, otherwise False"""</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">suffix</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">False</span>
<span class="n">suffix</span> <span class="o">=</span> <span class="n">suffix</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
<span class="k">if</span> <span class="n">suffix</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s2">&quot;.&quot;</span><span class="p">:</span>
<span class="k">if</span> <span class="n">suffix</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"."</span><span class="p">:</span>
<span class="n">suffix</span> <span class="o">=</span> <span class="n">suffix</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
<span class="k">return</span> <span class="p">(</span>
<span class="n">suffix</span> <span class="ow">in</span> <span class="n">EXIFTOOL_SUPPORTED_FILETYPES</span>
<span class="ow">and</span> <span class="n">EXIFTOOL_SUPPORTED_FILETYPES</span><span class="p">[</span><span class="n">suffix</span><span class="p">][</span><span class="s2">&quot;write&quot;</span><span class="p">]</span>
<span class="ow">and</span> <span class="n">EXIFTOOL_SUPPORTED_FILETYPES</span><span class="p">[</span><span class="n">suffix</span><span class="p">][</span><span class="s2">"write"</span><span class="p">]</span>
<span class="p">)</span>
<span class="k">def</span> <span class="nf">escape_str</span><span class="p">(</span><span class="n">s</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;escape string for use with exiftool -E&quot;&quot;&quot;</span>
<span class="sd">"""escape string for use with exiftool -E"""</span>
<span class="k">if</span> <span class="nb">type</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">str</span><span class="p">:</span>
<span class="k">return</span> <span class="n">s</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">html</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">,</span> <span class="s2">&quot;&amp;#xa;&quot;</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\t</span><span class="s2">&quot;</span><span class="p">,</span> <span class="s2">&quot;&amp;#x9;&quot;</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\r</span><span class="s2">&quot;</span><span class="p">,</span> <span class="s2">&quot;&amp;#xd;&quot;</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"&amp;#xa;"</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"</span><span class="se">\t</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"&amp;#x9;"</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"</span><span class="se">\r</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"&amp;#xd;"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">s</span>
<span class="k">def</span> <span class="nf">unescape_str</span><span class="p">(</span><span class="n">s</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;unescape an HTML string returned by exiftool -E&quot;&quot;&quot;</span>
<span class="sd">"""unescape an HTML string returned by exiftool -E"""</span>
<span class="k">if</span> <span class="nb">type</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">str</span><span class="p">:</span>
<span class="k">return</span> <span class="n">s</span>
<span class="c1"># avoid &quot; in values which result in json.loads() throwing an exception, #636</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot;&amp;quot;&quot;</span><span class="p">,</span> <span class="s1">&#39;</span><span class="se">\\</span><span class="s1">&quot;&#39;</span><span class="p">)</span>
<span class="c1"># avoid " in values which result in json.loads() throwing an exception, #636</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"&amp;quot;"</span><span class="p">,</span> <span class="s1">'</span><span class="se">\\</span><span class="s1">"'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">html</span><span class="o">.</span><span class="n">unescape</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
<span class="nd">@atexit</span><span class="o">.</span><span class="n">register</span>
<span class="k">def</span> <span class="nf">terminate_exiftool</span><span class="p">():</span>
<span class="sd">&quot;&quot;&quot;Terminate any running ExifTool subprocesses; call this to cleanup when done using ExifTool&quot;&quot;&quot;</span>
<span class="sd">"""Terminate any running ExifTool subprocesses; call this to cleanup when done using ExifTool"""</span>
<span class="k">for</span> <span class="n">proc</span> <span class="ow">in</span> <span class="n">EXIFTOOL_PROCESSES</span><span class="p">:</span>
<span class="n">proc</span><span class="o">.</span><span class="n">_stop_proc</span><span class="p">()</span>
<span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">get_exiftool_path</span><span class="p">():</span>
<span class="sd">&quot;&quot;&quot;return path of exiftool, cache result&quot;&quot;&quot;</span>
<span class="n">exiftool_path</span> <span class="o">=</span> <span class="n">shutil</span><span class="o">.</span><span class="n">which</span><span class="p">(</span><span class="s2">&quot;exiftool&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="n">exiftool_path</span><span class="p">:</span>
<span class="sd">"""return path of exiftool, cache result"""</span>
<span class="k">if</span> <span class="n">exiftool_path</span> <span class="o">:=</span> <span class="n">shutil</span><span class="o">.</span><span class="n">which</span><span class="p">(</span><span class="s2">"exiftool"</span><span class="p">):</span>
<span class="k">return</span> <span class="n">exiftool_path</span><span class="o">.</span><span class="n">rstrip</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span>
<span class="s2">&quot;Could not find exiftool. Please download and install from &quot;</span>
<span class="s2">&quot;https://exiftool.org/&quot;</span>
<span class="s2">"Could not find exiftool. Please download and install from "</span>
<span class="s2">"https://exiftool.org/"</span>
<span class="p">)</span>
<span class="k">class</span> <span class="nc">_ExifToolProc</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Runs exiftool in a subprocess via Popen</span>
<span class="sd"> Creates a singleton object&quot;&quot;&quot;</span>
<span class="sd">"""Runs exiftool in a subprocess via Popen</span>
<span class="sd"> Creates a singleton object"""</span>
<span class="k">def</span> <span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;create new object or return instance of already created singleton&quot;&quot;&quot;</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s2">&quot;instance&quot;</span><span class="p">)</span> <span class="ow">or</span> <span class="ow">not</span> <span class="bp">cls</span><span class="o">.</span><span class="n">instance</span><span class="p">:</span>
<span class="sd">"""create new object or return instance of already created singleton"""</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s2">"instance"</span><span class="p">)</span> <span class="ow">or</span> <span class="ow">not</span> <span class="bp">cls</span><span class="o">.</span><span class="n">instance</span><span class="p">:</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">instance</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">instance</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">exiftool</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;construct _ExifToolProc singleton object or return instance of already created object</span>
<span class="sd">"""construct _ExifToolProc singleton object or return instance of already created object</span>
<span class="sd"> exiftool: optional path to exiftool binary (if not provided, will search path to find it)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s2">&quot;_process_running&quot;</span><span class="p">)</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process_running</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s2">"_process_running"</span><span class="p">)</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process_running</span><span class="p">:</span>
<span class="c1"># already running</span>
<span class="k">if</span> <span class="n">exiftool</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">exiftool</span> <span class="o">!=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_exiftool</span><span class="p">:</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">&quot;exiftool subprocess already running, &quot;</span>
<span class="sa">f</span><span class="s2">&quot;ignoring exiftool=</span><span class="si">{</span><span class="n">exiftool</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="sa">f</span><span class="s2">"exiftool subprocess already running, "</span>
<span class="sa">f</span><span class="s2">"ignoring exiftool=</span><span class="si">{</span><span class="n">exiftool</span><span class="si">}</span><span class="s2">"</span>
<span class="p">)</span>
<span class="k">return</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process_running</span> <span class="o">=</span> <span class="kc">False</span>
@@ -157,7 +319,7 @@
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;return the exiftool subprocess&quot;&quot;&quot;</span>
<span class="sd">"""return the exiftool subprocess"""</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process_running</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process</span>
<span class="k">else</span><span class="p">:</span>
@@ -166,37 +328,37 @@
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">pid</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;return process id (PID) of the exiftool process&quot;&quot;&quot;</span>
<span class="sd">"""return process id (PID) of the exiftool process"""</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="o">.</span><span class="n">pid</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">exiftool</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;return path to exiftool process&quot;&quot;&quot;</span>
<span class="sd">"""return path to exiftool process"""</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_exiftool</span>
<span class="k">def</span> <span class="nf">_start_proc</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;start exiftool in batch mode&quot;&quot;&quot;</span>
<span class="sd">"""start exiftool in batch mode"""</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process_running</span><span class="p">:</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s2">&quot;exiftool already running: </span><span class="si">{self._process}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s2">"exiftool already running: </span><span class="si">{self._process}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">return</span>
<span class="c1"># open exiftool process</span>
<span class="c1"># make sure /usr/bin at start of path so exiftool can find xattr (see #636)</span>
<span class="n">env</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
<span class="n">env</span><span class="p">[</span><span class="s2">&quot;PATH&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;/usr/bin/:</span><span class="si">{</span><span class="n">env</span><span class="p">[</span><span class="s2">&quot;PATH&quot;</span><span class="p">]</span><span class="si">}</span><span class="s1">&#39;</span>
<span class="n">env</span><span class="p">[</span><span class="s2">"PATH"</span><span class="p">]</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'/usr/bin/:</span><span class="si">{</span><span class="n">env</span><span class="p">[</span><span class="s2">"PATH"</span><span class="p">]</span><span class="si">}</span><span class="s1">'</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">Popen</span><span class="p">(</span>
<span class="p">[</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_exiftool</span><span class="p">,</span>
<span class="s2">&quot;-stay_open&quot;</span><span class="p">,</span> <span class="c1"># keep process open in batch mode</span>
<span class="s2">&quot;True&quot;</span><span class="p">,</span> <span class="c1"># -stay_open=True, keep process open in batch mode</span>
<span class="s2">&quot;-@&quot;</span><span class="p">,</span> <span class="c1"># read command-line arguments from file</span>
<span class="s2">&quot;-&quot;</span><span class="p">,</span> <span class="c1"># read from stdin</span>
<span class="s2">&quot;-common_args&quot;</span><span class="p">,</span> <span class="c1"># specifies args common to all commands subsequently run</span>
<span class="s2">&quot;-n&quot;</span><span class="p">,</span> <span class="c1"># no print conversion (e.g. print tag values in machine readable format)</span>
<span class="s2">&quot;-P&quot;</span><span class="p">,</span> <span class="c1"># Preserve file modification date/time</span>
<span class="s2">&quot;-G&quot;</span><span class="p">,</span> <span class="c1"># print group name for each tag</span>
<span class="s2">&quot;-E&quot;</span><span class="p">,</span> <span class="c1"># escape tag values for HTML (allows use of HTML &amp;#xa; for newlines)</span>
<span class="s2">"-stay_open"</span><span class="p">,</span> <span class="c1"># keep process open in batch mode</span>
<span class="s2">"True"</span><span class="p">,</span> <span class="c1"># -stay_open=True, keep process open in batch mode</span>
<span class="s2">"-@"</span><span class="p">,</span> <span class="c1"># read command-line arguments from file</span>
<span class="s2">"-"</span><span class="p">,</span> <span class="c1"># read from stdin</span>
<span class="s2">"-common_args"</span><span class="p">,</span> <span class="c1"># specifies args common to all commands subsequently run</span>
<span class="s2">"-n"</span><span class="p">,</span> <span class="c1"># no print conversion (e.g. print tag values in machine readable format)</span>
<span class="s2">"-P"</span><span class="p">,</span> <span class="c1"># Preserve file modification date/time</span>
<span class="s2">"-G"</span><span class="p">,</span> <span class="c1"># print group name for each tag</span>
<span class="s2">"-E"</span><span class="p">,</span> <span class="c1"># escape tag values for HTML (allows use of HTML &amp;#xa; for newlines)</span>
<span class="p">],</span>
<span class="n">stdin</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">,</span>
<span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">,</span>
@@ -208,14 +370,14 @@
<span class="n">EXIFTOOL_PROCESSES</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_stop_proc</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;stop the exiftool process if it&#39;s running, otherwise, do nothing&quot;&quot;&quot;</span>
<span class="sd">"""stop the exiftool process if it's running, otherwise, do nothing"""</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process_running</span><span class="p">:</span>
<span class="k">return</span>
<span class="k">try</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">b</span><span class="s2">&quot;-stay_open</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">b</span><span class="s2">&quot;False</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">b</span><span class="s2">"-stay_open</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">b</span><span class="s2">"False</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">pass</span>
@@ -231,10 +393,10 @@
<div class="viewcode-block" id="ExifTool"><a class="viewcode-back" href="../../reference.html#osxphotos.ExifTool">[docs]</a><span class="k">class</span> <span class="nc">ExifTool</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Basic exiftool interface for reading and writing EXIF tags&quot;&quot;&quot;</span>
<span class="sd">"""Basic exiftool interface for reading and writing EXIF tags"""</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filepath</span><span class="p">,</span> <span class="n">exiftool</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">overwrite</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">flags</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Create ExifTool object</span>
<span class="sd">"""Create ExifTool object</span>
<span class="sd"> Args:</span>
<span class="sd"> file: path to image file</span>
@@ -244,7 +406,7 @@
<span class="sd"> Returns:</span>
<span class="sd"> ExifTool instance</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="bp">self</span><span class="o">.</span><span class="n">file</span> <span class="o">=</span> <span class="n">filepath</span>
<span class="bp">self</span><span class="o">.</span><span class="n">overwrite</span> <span class="o">=</span> <span class="n">overwrite</span>
<span class="bp">self</span><span class="o">.</span><span class="n">flags</span> <span class="o">=</span> <span class="n">flags</span> <span class="ow">or</span> <span class="p">[]</span>
@@ -261,7 +423,7 @@
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_exiftoolproc</span><span class="o">.</span><span class="n">process</span>
<div class="viewcode-block" id="ExifTool.setvalue"><a class="viewcode-back" href="../../reference.html#osxphotos.ExifTool.setvalue">[docs]</a> <span class="k">def</span> <span class="nf">setvalue</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tag</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Set tag to value(s); if value is None, will delete tag</span>
<span class="sd">"""Set tag to value(s); if value is None, will delete tag</span>
<span class="sd"> Args:</span>
<span class="sd"> tag: str; name of tag to set</span>
@@ -273,27 +435,27 @@
<span class="sd"> If error generated by exiftool, returns False and sets self.error to error string</span>
<span class="sd"> If warning generated by exiftool, returns True (unless there was also an error) and sets self.warning to warning string</span>
<span class="sd"> If called in context manager, returns True (execution is delayed until exiting context manager)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span>
<span class="n">value</span> <span class="o">=</span> <span class="s2">""</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">escape_str</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="n">command</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&quot;-</span><span class="si">{</span><span class="n">tag</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">]</span>
<span class="n">command</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">"-</span><span class="si">{</span><span class="n">tag</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">"</span><span class="p">]</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">overwrite</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_context_mgr</span><span class="p">:</span>
<span class="n">command</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&quot;-overwrite_original&quot;</span><span class="p">)</span>
<span class="n">command</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">"-overwrite_original"</span><span class="p">)</span>
<span class="c1"># avoid &quot;Warning: Some character(s) could not be encoded in Latin&quot; warning</span>
<span class="n">command</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&quot;-iptc:codedcharacterset=utf8&quot;</span><span class="p">)</span>
<span class="c1"># avoid "Warning: Some character(s) could not be encoded in Latin" warning</span>
<span class="n">command</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">"-iptc:codedcharacterset=utf8"</span><span class="p">)</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_context_mgr</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_commands</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">command</span><span class="p">)</span>
<span class="k">return</span> <span class="kc">True</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">error</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">run_commands</span><span class="p">(</span><span class="o">*</span><span class="n">command</span><span class="p">)</span>
<span class="k">return</span> <span class="n">error</span> <span class="o">==</span> <span class="s2">&quot;&quot;</span></div>
<span class="k">return</span> <span class="n">error</span> <span class="o">==</span> <span class="s2">""</span></div>
<div class="viewcode-block" id="ExifTool.addvalues"><a class="viewcode-back" href="../../reference.html#osxphotos.ExifTool.addvalues">[docs]</a> <span class="k">def</span> <span class="nf">addvalues</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tag</span><span class="p">,</span> <span class="o">*</span><span class="n">values</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Add one or more value(s) to tag</span>
<span class="sd">"""Add one or more value(s) to tag</span>
<span class="sd"> If more than one value is passed, each value will be added to the tag</span>
<span class="sd"> Args:</span>
@@ -311,32 +473,32 @@
<span class="sd"> the values being added are not already in the EXIF data</span>
<span class="sd"> For some tags, such as IPTC:Keywords, this will add a new value to the list of keywords,</span>
<span class="sd"> but for others, such as EXIF:ISO, this will literally add a value to the existing value.</span>
<span class="sd"> It&#39;s up to the caller to know what exiftool will do for each tag</span>
<span class="sd"> It's up to the caller to know what exiftool will do for each tag</span>
<span class="sd"> If setvalue called before addvalues, exiftool does not appear to add duplicates,</span>
<span class="sd"> but if addvalues called without first calling setvalue, exiftool will add duplicate values</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">values</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&quot;Must pass at least one value&quot;</span><span class="p">)</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"Must pass at least one value"</span><span class="p">)</span>
<span class="n">command</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">values</span><span class="p">:</span>
<span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&quot;Can&#39;t add None value to tag&quot;</span><span class="p">)</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"Can't add None value to tag"</span><span class="p">)</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">escape_str</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="n">command</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;-</span><span class="si">{</span><span class="n">tag</span><span class="si">}</span><span class="s2">+=</span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">command</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"-</span><span class="si">{</span><span class="n">tag</span><span class="si">}</span><span class="s2">+=</span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">overwrite</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_context_mgr</span><span class="p">:</span>
<span class="n">command</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&quot;-overwrite_original&quot;</span><span class="p">)</span>
<span class="n">command</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">"-overwrite_original"</span><span class="p">)</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_context_mgr</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_commands</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">command</span><span class="p">)</span>
<span class="k">return</span> <span class="kc">True</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">error</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">run_commands</span><span class="p">(</span><span class="o">*</span><span class="n">command</span><span class="p">)</span>
<span class="k">return</span> <span class="n">error</span> <span class="o">==</span> <span class="s2">&quot;&quot;</span></div>
<span class="k">return</span> <span class="n">error</span> <span class="o">==</span> <span class="s2">""</span></div>
<div class="viewcode-block" id="ExifTool.run_commands"><a class="viewcode-back" href="../../reference.html#osxphotos.ExifTool.run_commands">[docs]</a> <span class="k">def</span> <span class="nf">run_commands</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">commands</span><span class="p">,</span> <span class="n">no_file</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Run commands in the exiftool process and return result.</span>
<span class="sd">"""Run commands in the exiftool process and return result.</span>
<span class="sd"> Args:</span>
<span class="sd"> *commands: exiftool commands to run</span>
@@ -350,35 +512,35 @@
<span class="sd"> error: if exiftool generated errors, string containing otherwise empty string</span>
<span class="sd"> Note: Also sets self.warning and self.error if warning or error generated.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">if</span> <span class="ow">not</span> <span class="p">(</span><span class="nb">hasattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s2">&quot;_process&quot;</span><span class="p">)</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&quot;exiftool process is not running&quot;</span><span class="p">)</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="ow">not</span> <span class="p">(</span><span class="nb">hasattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s2">"_process"</span><span class="p">)</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"exiftool process is not running"</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">commands</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="s2">&quot;must provide one or more command to run&quot;</span><span class="p">)</span>
<span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="s2">"must provide one or more command to run"</span><span class="p">)</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_context_mgr</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">overwrite</span><span class="p">:</span>
<span class="n">commands</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">commands</span><span class="p">)</span>
<span class="n">commands</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&quot;-overwrite_original&quot;</span><span class="p">)</span>
<span class="n">commands</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">"-overwrite_original"</span><span class="p">)</span>
<span class="n">filename</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">fsencode</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">file</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">no_file</span> <span class="k">else</span> <span class="sa">b</span><span class="s2">&quot;&quot;</span>
<span class="n">filename</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">fsencode</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">file</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">no_file</span> <span class="k">else</span> <span class="sa">b</span><span class="s2">""</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">flags</span><span class="p">:</span>
<span class="c1"># need to split flags, e.g. so &quot;--ext AVI&quot; becomes [&quot;--ext&quot;, &quot;AVI&quot;]</span>
<span class="c1"># need to split flags, e.g. so "--ext AVI" becomes ["--ext", "AVI"]</span>
<span class="n">flags</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">flags</span><span class="p">:</span>
<span class="n">flags</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">split</span><span class="p">())</span>
<span class="n">command_str</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">f</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">flags</span><span class="p">])</span>
<span class="n">command_str</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span>
<span class="n">command_str</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">f</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">flags</span><span class="p">])</span>
<span class="n">command_str</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">command_str</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">&quot;&quot;</span>
<span class="n">command_str</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">""</span>
<span class="n">command_str</span> <span class="o">+=</span> <span class="p">(</span>
<span class="sa">b</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">c</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">commands</span><span class="p">])</span>
<span class="o">+</span> <span class="sa">b</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span>
<span class="sa">b</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">c</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">commands</span><span class="p">])</span>
<span class="o">+</span> <span class="sa">b</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span>
<span class="o">+</span> <span class="n">filename</span>
<span class="o">+</span> <span class="sa">b</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span>
<span class="o">+</span> <span class="sa">b</span><span class="s2">&quot;-execute</span><span class="se">\n</span><span class="s2">&quot;</span>
<span class="o">+</span> <span class="sa">b</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span>
<span class="o">+</span> <span class="sa">b</span><span class="s2">"-execute</span><span class="se">\n</span><span class="s2">"</span>
<span class="p">)</span>
<span class="c1"># send the command</span>
@@ -386,19 +548,19 @@
<span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
<span class="c1"># read the output</span>
<span class="n">output</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">&quot;&quot;</span>
<span class="n">warning</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">&quot;&quot;</span>
<span class="n">error</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">&quot;&quot;</span>
<span class="n">output</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">""</span>
<span class="n">warning</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">""</span>
<span class="n">error</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">""</span>
<span class="k">while</span> <span class="n">EXIFTOOL_STAYOPEN_EOF</span> <span class="ow">not</span> <span class="ow">in</span> <span class="nb">str</span><span class="p">(</span><span class="n">output</span><span class="p">):</span>
<span class="n">line</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">readline</span><span class="p">()</span>
<span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="sa">b</span><span class="s2">&quot;Warning&quot;</span><span class="p">):</span>
<span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="sa">b</span><span class="s2">"Warning"</span><span class="p">):</span>
<span class="n">warning</span> <span class="o">+=</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
<span class="k">elif</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="sa">b</span><span class="s2">&quot;Error&quot;</span><span class="p">):</span>
<span class="k">elif</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="sa">b</span><span class="s2">"Error"</span><span class="p">):</span>
<span class="n">error</span> <span class="o">+=</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">output</span> <span class="o">+=</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
<span class="n">warning</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span> <span class="k">if</span> <span class="n">warning</span> <span class="o">==</span> <span class="sa">b</span><span class="s2">&quot;&quot;</span> <span class="k">else</span> <span class="n">warning</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span>
<span class="n">error</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span> <span class="k">if</span> <span class="n">error</span> <span class="o">==</span> <span class="sa">b</span><span class="s2">&quot;&quot;</span> <span class="k">else</span> <span class="n">error</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span>
<span class="n">warning</span> <span class="o">=</span> <span class="s2">""</span> <span class="k">if</span> <span class="n">warning</span> <span class="o">==</span> <span class="sa">b</span><span class="s2">""</span> <span class="k">else</span> <span class="n">warning</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">)</span>
<span class="n">error</span> <span class="o">=</span> <span class="s2">""</span> <span class="k">if</span> <span class="n">error</span> <span class="o">==</span> <span class="sa">b</span><span class="s2">""</span> <span class="k">else</span> <span class="n">error</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">warning</span> <span class="o">=</span> <span class="n">warning</span>
<span class="bp">self</span><span class="o">.</span><span class="n">error</span> <span class="o">=</span> <span class="n">error</span>
@@ -406,41 +568,41 @@
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">pid</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;return process id (PID) of the exiftool process&quot;&quot;&quot;</span>
<span class="sd">"""return process id (PID) of the exiftool process"""</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="o">.</span><span class="n">pid</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">version</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;returns exiftool version&quot;&quot;&quot;</span>
<span class="n">ver</span><span class="p">,</span> <span class="n">_</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">run_commands</span><span class="p">(</span><span class="s2">&quot;-ver&quot;</span><span class="p">,</span> <span class="n">no_file</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">return</span> <span class="n">ver</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span>
<span class="sd">"""returns exiftool version"""</span>
<span class="n">ver</span><span class="p">,</span> <span class="n">_</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">run_commands</span><span class="p">(</span><span class="s2">"-ver"</span><span class="p">,</span> <span class="n">no_file</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">return</span> <span class="n">ver</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">)</span>
<div class="viewcode-block" id="ExifTool.asdict"><a class="viewcode-back" href="../../reference.html#osxphotos.ExifTool.asdict">[docs]</a> <span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tag_groups</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">normalized</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;return dictionary of all EXIF tags and values from exiftool</span>
<span class="sd">"""return dictionary of all EXIF tags and values from exiftool</span>
<span class="sd"> returns empty dict if no tags</span>
<span class="sd"> Args:</span>
<span class="sd"> tag_groups: if True (default), dict keys have tag groups, e.g. &quot;IPTC:Keywords&quot;; if False, drops groups from keys, e.g. &quot;Keywords&quot;</span>
<span class="sd"> tag_groups: if True (default), dict keys have tag groups, e.g. "IPTC:Keywords"; if False, drops groups from keys, e.g. "Keywords"</span>
<span class="sd"> normalized: if True, dict keys are all normalized to lower case (default is False)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">json_str</span><span class="p">,</span> <span class="n">_</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">run_commands</span><span class="p">(</span><span class="s2">&quot;-json&quot;</span><span class="p">)</span>
<span class="sd"> """</span>
<span class="n">json_str</span><span class="p">,</span> <span class="n">_</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">run_commands</span><span class="p">(</span><span class="s2">"-json"</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">json_str</span><span class="p">:</span>
<span class="k">return</span> <span class="nb">dict</span><span class="p">()</span>
<span class="n">json_str</span> <span class="o">=</span> <span class="n">unescape_str</span><span class="p">(</span><span class="n">json_str</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">))</span>
<span class="n">json_str</span> <span class="o">=</span> <span class="n">unescape_str</span><span class="p">(</span><span class="n">json_str</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">))</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">exifdict</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">json_str</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="c1"># will fail with some commands, e.g --ext AVI which produces</span>
<span class="c1"># &#39;No file with specified extension&#39; instead of json</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;error loading json returned by exiftool: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">json_str</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="c1"># 'No file with specified extension' instead of json</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">"error loading json returned by exiftool: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">json_str</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">return</span> <span class="nb">dict</span><span class="p">()</span>
<span class="n">exifdict</span> <span class="o">=</span> <span class="n">exifdict</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">tag_groups</span><span class="p">:</span>
<span class="c1"># strip tag groups</span>
<span class="n">exif_new</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">exifdict</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="n">k</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sa">r</span><span class="s2">&quot;.*:&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span>
<span class="n">k</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sa">r</span><span class="s2">".*:"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span>
<span class="n">exif_new</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="n">v</span>
<span class="n">exifdict</span> <span class="o">=</span> <span class="n">exif_new</span>
@@ -450,18 +612,18 @@
<span class="k">return</span> <span class="n">exifdict</span></div>
<div class="viewcode-block" id="ExifTool.json"><a class="viewcode-back" href="../../reference.html#osxphotos.ExifTool.json">[docs]</a> <span class="k">def</span> <span class="nf">json</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;returns JSON string containing all EXIF tags and values from exiftool&quot;&quot;&quot;</span>
<span class="n">json</span><span class="p">,</span> <span class="n">_</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">run_commands</span><span class="p">(</span><span class="s2">&quot;-json&quot;</span><span class="p">)</span>
<span class="n">json</span> <span class="o">=</span> <span class="n">unescape_str</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">))</span>
<span class="sd">"""returns JSON string containing all EXIF tags and values from exiftool"""</span>
<span class="n">json</span><span class="p">,</span> <span class="n">_</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">run_commands</span><span class="p">(</span><span class="s2">"-json"</span><span class="p">)</span>
<span class="n">json</span> <span class="o">=</span> <span class="n">unescape_str</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">))</span>
<span class="k">return</span> <span class="n">json</span></div>
<span class="k">def</span> <span class="nf">_read_exif</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;read exif data from file&quot;&quot;&quot;</span>
<span class="sd">"""read exif data from file"""</span>
<span class="n">data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">data</span> <span class="o">=</span> <span class="p">{</span><span class="n">k</span><span class="p">:</span> <span class="n">v</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">items</span><span class="p">()}</span>
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">&quot;file: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">file</span><span class="si">}</span><span class="se">\n</span><span class="s2">exiftool: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_exiftoolproc</span><span class="o">.</span><span class="n">_exiftool</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">"file: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">file</span><span class="si">}</span><span class="se">\n</span><span class="s2">exiftool: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_exiftoolproc</span><span class="o">.</span><span class="n">_exiftool</span><span class="si">}</span><span class="s2">"</span>
<span class="k">def</span> <span class="fm">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_context_mgr</span> <span class="o">=</span> <span class="kc">True</span>
@@ -477,15 +639,15 @@
<span class="k">class</span> <span class="nc">ExifToolCaching</span><span class="p">(</span><span class="n">ExifTool</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Basic exiftool interface for reading and writing EXIF tags, with caching.</span>
<span class="sd"> Use this only when you know the file&#39;s EXIF data will not be changed by any external process.</span>
<span class="sd">"""Basic exiftool interface for reading and writing EXIF tags, with caching.</span>
<span class="sd"> Use this only when you know the file's EXIF data will not be changed by any external process.</span>
<span class="sd"> Creates a singleton cached ExifTool instance&quot;&quot;&quot;</span>
<span class="sd"> Creates a singleton cached ExifTool instance"""</span>
<span class="n">_singletons</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">def</span> <span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">filepath</span><span class="p">,</span> <span class="n">exiftool</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;create new object or return instance of already created singleton&quot;&quot;&quot;</span>
<span class="sd">"""create new object or return instance of already created singleton"""</span>
<span class="k">if</span> <span class="n">filepath</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_singletons</span><span class="p">:</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">_singletons</span><span class="p">[</span><span class="n">filepath</span><span class="p">]</span> <span class="o">=</span> <span class="n">_ExifToolCaching</span><span class="p">(</span><span class="n">filepath</span><span class="p">,</span> <span class="n">exiftool</span><span class="o">=</span><span class="n">exiftool</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_singletons</span><span class="p">[</span><span class="n">filepath</span><span class="p">]</span>
@@ -493,7 +655,7 @@
<span class="k">class</span> <span class="nc">_ExifToolCaching</span><span class="p">(</span><span class="n">ExifTool</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filepath</span><span class="p">,</span> <span class="n">exiftool</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Create read-only ExifTool object that caches values</span>
<span class="sd">"""Create read-only ExifTool object that caches values</span>
<span class="sd"> Args:</span>
<span class="sd"> file: path to image file</span>
@@ -501,21 +663,21 @@
<span class="sd"> Returns:</span>
<span class="sd"> ExifTool instance</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_json_cache</span> <span class="o">=</span> <span class="kc">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_asdict_cache</span> <span class="o">=</span> <span class="p">{}</span>
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">filepath</span><span class="p">,</span> <span class="n">exiftool</span><span class="o">=</span><span class="n">exiftool</span><span class="p">,</span> <span class="n">overwrite</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">flags</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">run_commands</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">commands</span><span class="p">,</span> <span class="n">no_file</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
<span class="k">if</span> <span class="n">commands</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">&quot;-json&quot;</span><span class="p">,</span> <span class="s2">&quot;-ver&quot;</span><span class="p">]:</span>
<span class="k">raise</span> <span class="ne">NotImplementedError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="si">}</span><span class="s2"> is read-only&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="n">commands</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"-json"</span><span class="p">,</span> <span class="s2">"-ver"</span><span class="p">]:</span>
<span class="k">raise</span> <span class="ne">NotImplementedError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="si">}</span><span class="s2"> is read-only"</span><span class="p">)</span>
<span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">run_commands</span><span class="p">(</span><span class="o">*</span><span class="n">commands</span><span class="p">,</span> <span class="n">no_file</span><span class="o">=</span><span class="n">no_file</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">setvalue</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tag</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">NotImplementedError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="si">}</span><span class="s2"> is read-only&quot;</span><span class="p">)</span>
<span class="k">raise</span> <span class="ne">NotImplementedError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="si">}</span><span class="s2"> is read-only"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">addvalues</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tag</span><span class="p">,</span> <span class="o">*</span><span class="n">values</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">NotImplementedError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="si">}</span><span class="s2"> is read-only&quot;</span><span class="p">)</span>
<span class="k">raise</span> <span class="ne">NotImplementedError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="si">}</span><span class="s2"> is read-only"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">json</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_json_cache</span><span class="p">:</span>
@@ -523,13 +685,13 @@
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_json_cache</span>
<span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tag_groups</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">normalized</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;return dictionary of all EXIF tags and values from exiftool</span>
<span class="sd">"""return dictionary of all EXIF tags and values from exiftool</span>
<span class="sd"> returns empty dict if no tags</span>
<span class="sd"> Args:</span>
<span class="sd"> tag_groups: if True (default), dict keys have tag groups, e.g. &quot;IPTC:Keywords&quot;; if False, drops groups from keys, e.g. &quot;Keywords&quot;</span>
<span class="sd"> tag_groups: if True (default), dict keys have tag groups, e.g. "IPTC:Keywords"; if False, drops groups from keys, e.g. "Keywords"</span>
<span class="sd"> normalized: if True, dict keys are all normalized to lower case (default is False)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</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">_asdict_cache</span><span class="p">[</span><span class="n">tag_groups</span><span class="p">][</span><span class="n">normalized</span><span class="p">]</span>
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
@@ -541,76 +703,49 @@
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_asdict_cache</span><span class="p">[</span><span class="n">tag_groups</span><span class="p">][</span><span class="n">normalized</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">flush_cache</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Clear cached data so that calls to json or asdict return fresh data&quot;&quot;&quot;</span>
<span class="sd">"""Clear cached data so that calls to json or asdict return fresh data"""</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_json_cache</span> <span class="o">=</span> <span class="kc">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_asdict_cache</span> <span class="o">=</span> <span class="p">{}</span>
</pre></div>
</div>
</article>
</div>
<footer>
<div class="related-pages">
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h1 class="logo"><a href="../../index.html">osxphotos</a></h1>
<h3>Navigation</h3>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../overview.html">osxphotos</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../tutorial.html">Tutorial</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../cli.html">osxphotos command line interface (CLI)</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../reference.html">osxphotos package</a></li>
</ul>
<div class="relations">
<h3>Related Topics</h3>
<ul>
<li><a href="../../index.html">Documentation overview</a><ul>
<li><a href="../index.html">Module code</a><ul>
</ul></li>
</ul></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3 id="searchlabel">Quick search</h3>
<div class="searchformwrapper">
<form class="search" action="../../search.html" method="get">
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
<input type="submit" value="Go" />
</form>
</div>
</div>
<script>$('#searchbox').show(0);</script>
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2021, Rhet Turnbull
</div>
Made with <a href="https://www.sphinx-doc.org/">Sphinx</a> and <a class="muted-link" href="https://pradyunsg.me">@pradyunsg</a>'s
<a href="https://github.com/pradyunsg/furo">Furo</a>
</div>
<div class="right-details">
<div class="icons">
</div>
</div>
</div>
</div>
<div class="clearer"></div>
</footer>
</div>
<div class="footer">
&copy;2021, Rhet Turnbull.
<aside class="toc-drawer no-toc">
|
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.4.0</a>
&amp; <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
</div>
</body>
</aside>
</div>
</div><script data-url_root="../../" id="documentation_options" src="../../_static/documentation_options.js"></script>
<script src="../../_static/jquery.js"></script>
<script src="../../_static/underscore.js"></script>
<script src="../../_static/doctools.js"></script>
<script src="../../_static/scripts/furo.js"></script>
<script src="../../_static/clipboard.min.js"></script>
<script src="../../_static/copybutton.js"></script>
</body>
</html>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.export_db - osxphotos 0.47.13 documentation</title>
<title>osxphotos.export_db - osxphotos 0.49.1 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.47.13 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.49.1 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.47.13 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.1 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -197,18 +197,21 @@
<span></span><span class="sd">""" Helper class for managing database used by PhotoExporter for tracking state of exports and updates """</span>
<span class="kn">import</span> <span class="nn">contextlib</span>
<span class="kn">import</span> <span class="nn">datetime</span>
<span class="kn">import</span> <span class="nn">gzip</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">logging</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">pathlib</span>
<span class="kn">import</span> <span class="nn">pickle</span>
<span class="kn">import</span> <span class="nn">sqlite3</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">suppress</span>
<span class="kn">from</span> <span class="nn">io</span> <span class="kn">import</span> <span class="n">StringIO</span>
<span class="kn">from</span> <span class="nn">sqlite3</span> <span class="kn">import</span> <span class="n">Error</span>
<span class="kn">from</span> <span class="nn">tempfile</span> <span class="kn">import</span> <span class="n">TemporaryDirectory</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Tuple</span><span class="p">,</span> <span class="n">Union</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Tuple</span><span class="p">,</span> <span class="n">Union</span>
<span class="kn">from</span> <span class="nn">tenacity</span> <span class="kn">import</span> <span class="n">retry</span><span class="p">,</span> <span class="n">stop_after_attempt</span>
@@ -223,12 +226,42 @@
<span class="s2">"ExportDBTemp"</span><span class="p">,</span>
<span class="p">]</span>
<span class="n">OSXPHOTOS_EXPORTDB_VERSION</span> <span class="o">=</span> <span class="s2">"6.0"</span>
<span class="n">OSXPHOTOS_EXPORTDB_VERSION</span> <span class="o">=</span> <span class="s2">"7.0"</span>
<span class="n">OSXPHOTOS_ABOUT_STRING</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"Created by osxphotos version </span><span class="si">{</span><span class="n">__version__</span><span class="si">}</span><span class="s2"> (https://github.com/RhetTbull/osxphotos) on </span><span class="si">{</span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="si">}</span><span class="s2">"</span>
<span class="c1"># max retry attempts for methods which use tenacity.retry</span>
<span class="n">MAX_RETRY_ATTEMPTS</span> <span class="o">=</span> <span class="mi">5</span>
<span class="c1"># maximum number of export results rows to save</span>
<span class="n">MAX_EXPORT_RESULTS_DATA_ROWS</span> <span class="o">=</span> <span class="mi">10</span>
<span class="k">def</span> <span class="nf">pickle_and_zip</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bytes</span><span class="p">:</span>
<span class="sd">"""</span>
<span class="sd"> Pickle and gzip data.</span>
<span class="sd"> Args:</span>
<span class="sd"> data: data to pickle and gzip (must be pickle-able)</span>
<span class="sd"> Returns:</span>
<span class="sd"> bytes of gzipped pickled data</span>
<span class="sd"> """</span>
<span class="n">pickled</span> <span class="o">=</span> <span class="n">pickle</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">return</span> <span class="n">gzip</span><span class="o">.</span><span class="n">compress</span><span class="p">(</span><span class="n">pickled</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">unzip_and_unpickle</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">bytes</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
<span class="sd">"""</span>
<span class="sd"> Unzip and unpickle data.</span>
<span class="sd"> Args:</span>
<span class="sd"> data: data to unzip and unpickle</span>
<span class="sd"> Returns:</span>
<span class="sd"> unpickled data</span>
<span class="sd"> """</span>
<span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">gzip</span><span class="o">.</span><span class="n">decompress</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
<div class="viewcode-block" id="ExportDB"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB">[docs]</a><span class="k">class</span> <span class="nc">ExportDB</span><span class="p">:</span>
<span class="sd">"""Interface to sqlite3 database used to store state information for osxphotos export command"""</span>
@@ -388,6 +421,63 @@
<span class="k">except</span> <span class="n">Error</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="n">e</span><span class="p">)</span></div>
<div class="viewcode-block" id="ExportDB.set_export_results"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.set_export_results">[docs]</a> <span class="k">def</span> <span class="nf">set_export_results</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">results</span><span class="p">):</span>
<span class="sd">"""Store export results in database; data is pickled and gzipped for storage"""</span>
<span class="n">results_data</span> <span class="o">=</span> <span class="n">pickle_and_zip</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
<span class="n">conn</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_conn</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">dt</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="o">.</span><span class="n">isoformat</span><span class="p">()</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
<span class="sd">"""</span>
<span class="sd"> UPDATE export_results_data</span>
<span class="sd"> SET datetime = ?,</span>
<span class="sd"> export_results = ?</span>
<span class="sd"> WHERE datetime = (SELECT MIN(datetime) FROM export_results_data);</span>
<span class="sd"> """</span><span class="p">,</span>
<span class="p">(</span><span class="n">dt</span><span class="p">,</span> <span class="n">results_data</span><span class="p">),</span>
<span class="p">)</span>
<span class="n">conn</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
<span class="k">except</span> <span class="n">Error</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="n">e</span><span class="p">)</span></div>
<div class="viewcode-block" id="ExportDB.get_export_results"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_export_results">[docs]</a> <span class="k">def</span> <span class="nf">get_export_results</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">run</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">):</span>
<span class="sd">"""Retrieve export results from database</span>
<span class="sd"> Args:</span>
<span class="sd"> run: which run to retrieve results for;</span>
<span class="sd"> 0 = most recent run, -1 = previous run, -2 = run prior to that, etc.</span>
<span class="sd"> Returns:</span>
<span class="sd"> ExportResults object or None if no results found</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="n">run</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"run must be 0 or negative"</span><span class="p">)</span>
<span class="n">run</span> <span class="o">=</span> <span class="o">-</span><span class="n">run</span>
<span class="n">conn</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_conn</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
<span class="sd">"""</span>
<span class="sd"> SELECT export_results</span>
<span class="sd"> FROM export_results_data</span>
<span class="sd"> ORDER BY datetime DESC</span>
<span class="sd"> """</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">rows</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">fetchall</span><span class="p">()</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">rows</span><span class="p">[</span><span class="n">run</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>
<span class="n">results</span> <span class="o">=</span> <span class="n">unzip_and_unpickle</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="k">if</span> <span class="n">data</span> <span class="k">else</span> <span class="kc">None</span>
<span class="k">except</span> <span class="ne">IndexError</span><span class="p">:</span>
<span class="n">results</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">except</span> <span class="n">Error</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="n">results</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">return</span> <span class="n">results</span></div>
<div class="viewcode-block" id="ExportDB.close"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.close">[docs]</a> <span class="k">def</span> <span class="nf">close</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""close the database connection"""</span>
<span class="k">try</span><span class="p">:</span>
@@ -558,12 +648,16 @@
<span class="c1"># create export_data table</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_migrate_5_0_to_6_0</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
<span class="k">if</span> <span class="n">version</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">&lt;</span> <span class="s2">"7.0"</span><span class="p">:</span>
<span class="c1"># create report_data table</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_migrate_6_0_to_7_0</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
<span class="n">conn</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"VACUUM;"</span><span class="p">)</span>
<span class="n">conn</span><span class="o">.</span><span class="n">commit</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="sd">"""ensure the database connection is closed"""</span>
<span class="k">with</span> <span class="n">contextlib</span><span class="o">.</span><span class="n">suppress</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
<span class="k">with</span> <span class="n">suppress</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_conn</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">_insert_run_info</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
@@ -752,6 +846,29 @@
<span class="k">except</span> <span class="n">Error</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_migrate_6_0_to_7_0</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">conn</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
<span class="sd">"""CREATE TABLE IF NOT EXISTS export_results_data (</span>
<span class="sd"> id INTEGER PRIMARY KEY,</span>
<span class="sd"> datetime TEXT,</span>
<span class="sd"> export_results BLOB</span>
<span class="sd"> );"""</span>
<span class="p">)</span>
<span class="c1"># pre-populate report_data table with blank fields</span>
<span class="c1"># ExportDB will use these as circular buffer always writing to the oldest record</span>
<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">MAX_EXPORT_RESULTS_DATA_ROWS</span><span class="p">):</span>
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
<span class="sd">"""INSERT INTO export_results_data (datetime, export_results) VALUES (?, ?);"""</span><span class="p">,</span>
<span class="p">(</span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="o">.</span><span class="n">isoformat</span><span class="p">(),</span> <span class="sa">b</span><span class="s2">""</span><span class="p">),</span>
<span class="p">)</span>
<span class="c1"># sleep a tiny bit just to ensure time stamps increment</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.001</span><span class="p">)</span>
<span class="n">conn</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
<span class="k">except</span> <span class="n">Error</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_perform_db_maintenace</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">conn</span><span class="p">):</span>
<span class="sd">"""Perform database maintenance"""</span>
<span class="k">try</span><span class="p">:</span>
@@ -826,7 +943,7 @@
<span class="k">except</span> <span class="n">Error</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_open_export_db</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dbfile</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">_open_export_db</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dbfile</span><span class="p">):</span> <span class="c1"># sourcery skip: raise-specific-error</span>
<span class="sd">"""open export database and return a db connection</span>
<span class="sd"> returns: connection to the database</span>
<span class="sd"> """</span>
@@ -877,7 +994,7 @@
<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="sd">"""close the database connection"""</span>
<span class="k">with</span> <span class="n">contextlib</span><span class="o">.</span><span class="n">suppress</span><span class="p">(</span><span class="n">Error</span><span class="p">):</span>
<span class="k">with</span> <span class="n">suppress</span><span class="p">(</span><span class="n">Error</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
@@ -1129,6 +1246,10 @@
<span class="s2">"photoinfo"</span><span class="p">:</span> <span class="n">photoinfo</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">def</span> <span class="nf">json</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="sd">"""Return json of self"""</span>
<span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">asdict</span><span class="p">(),</span> <span class="n">indent</span><span class="o">=</span><span class="n">indent</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_context_manager</span> <span class="o">=</span> <span class="kc">True</span>
<span class="k">return</span> <span class="bp">self</span>

View File

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

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.photoexporter - osxphotos 0.47.9 documentation</title>
<title>osxphotos.photoexporter - osxphotos 0.49.0 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.47.9 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.49.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.47.9 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -155,11 +155,12 @@
</form>
<div id="searchbox"></div><div class="sidebar-scroll"><div class="sidebar-tree">
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../overview.html">osxphotos</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../tutorial.html">osxphotos Tutorial</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../cli.html">osxphotos Command Line Interface (CLI)</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../package_overview.html">Example uses of the python package</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../reference.html">osxphotos python API</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../overview.html">OSXPhotos</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../tutorial.html">OSXPhotos Tutorial</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../cli.html">OSXPhotos Command Line Interface (CLI)</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../template_help.html">OSXPhotos Template System</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../package_overview.html">OSXPhotos Python Package Overview</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../reference.html">OSXPhotos python API</a></li>
</ul>
</div>
@@ -197,16 +198,15 @@
<span class="sd">"""</span>
<span class="kn">import</span> <span class="nn">dataclasses</span>
<span class="kn">import</span> <span class="nn">hashlib</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">logging</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">pathlib</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="kn">import</span> <span class="nn">tempfile</span>
<span class="kn">import</span> <span class="nn">typing</span> <span class="k">as</span> <span class="nn">t</span>
<span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">namedtuple</span> <span class="c1"># pylint: disable=syntax-error</span>
<span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">asdict</span><span class="p">,</span> <span class="n">dataclass</span>
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
<span class="kn">from</span> <span class="nn">enum</span> <span class="kn">import</span> <span class="n">Enum</span>
<span class="kn">import</span> <span class="nn">photoscript</span>
@@ -240,14 +240,13 @@
<span class="kn">from</span> <span class="nn">.phototemplate</span> <span class="kn">import</span> <span class="n">RenderOptions</span>
<span class="kn">from</span> <span class="nn">.rich_utils</span> <span class="kn">import</span> <span class="n">add_rich_markup_tag</span>
<span class="kn">from</span> <span class="nn">.uti</span> <span class="kn">import</span> <span class="n">get_preferred_uti_extension</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">increment_filename</span><span class="p">,</span> <span class="n">lineno</span><span class="p">,</span> <span class="n">list_directory</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">hexdigest</span><span class="p">,</span> <span class="n">increment_filename</span><span class="p">,</span> <span class="n">lineno</span><span class="p">,</span> <span class="n">list_directory</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span>
<span class="s2">"ExportError"</span><span class="p">,</span>
<span class="s2">"ExportOptions"</span><span class="p">,</span>
<span class="s2">"ExportResults"</span><span class="p">,</span>
<span class="s2">"PhotoExporter"</span><span class="p">,</span>
<span class="s2">"hexdigest"</span><span class="p">,</span>
<span class="s2">"rename_jpeg_files"</span><span class="p">,</span>
<span class="p">]</span>
@@ -436,57 +435,62 @@
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span>
<span class="n">exported</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">new</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">updated</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">skipped</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">exif_updated</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">touched</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">to_touch</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">converted_to_jpeg</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">sidecar_json_written</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">sidecar_json_skipped</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">sidecar_exiftool_written</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">sidecar_exiftool_skipped</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">sidecar_xmp_written</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">sidecar_xmp_skipped</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">missing</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">error</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">exiftool_warning</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">exiftool_error</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">xattr_written</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">xattr_skipped</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">deleted_files</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">deleted_directories</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">deleted_files</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">error</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">exif_updated</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">exiftool_error</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">exiftool_warning</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">exported_album</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">skipped_album</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">exported</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">metadata_changed</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">missing_album</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">missing</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">new</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">sidecar_exiftool_skipped</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">sidecar_exiftool_written</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">sidecar_json_skipped</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">sidecar_json_written</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">sidecar_xmp_skipped</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">sidecar_xmp_written</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">skipped_album</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">skipped</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">to_touch</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">touched</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">updated</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">xattr_skipped</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">xattr_written</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">exported</span> <span class="o">=</span> <span class="n">exported</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">new</span> <span class="o">=</span> <span class="n">new</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">updated</span> <span class="o">=</span> <span class="n">updated</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">skipped</span> <span class="o">=</span> <span class="n">skipped</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">exif_updated</span> <span class="o">=</span> <span class="n">exif_updated</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">touched</span> <span class="o">=</span> <span class="n">touched</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">to_touch</span> <span class="o">=</span> <span class="n">to_touch</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">datetime</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="o">.</span><span class="n">isoformat</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">converted_to_jpeg</span> <span class="o">=</span> <span class="n">converted_to_jpeg</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sidecar_json_written</span> <span class="o">=</span> <span class="n">sidecar_json_written</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sidecar_json_skipped</span> <span class="o">=</span> <span class="n">sidecar_json_skipped</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sidecar_exiftool_written</span> <span class="o">=</span> <span class="n">sidecar_exiftool_written</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sidecar_exiftool_skipped</span> <span class="o">=</span> <span class="n">sidecar_exiftool_skipped</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sidecar_xmp_written</span> <span class="o">=</span> <span class="n">sidecar_xmp_written</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sidecar_xmp_skipped</span> <span class="o">=</span> <span class="n">sidecar_xmp_skipped</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">missing</span> <span class="o">=</span> <span class="n">missing</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">error</span> <span class="o">=</span> <span class="n">error</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">exiftool_warning</span> <span class="o">=</span> <span class="n">exiftool_warning</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">exiftool_error</span> <span class="o">=</span> <span class="n">exiftool_error</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">xattr_written</span> <span class="o">=</span> <span class="n">xattr_written</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">xattr_skipped</span> <span class="o">=</span> <span class="n">xattr_skipped</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">deleted_files</span> <span class="o">=</span> <span class="n">deleted_files</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">deleted_directories</span> <span class="o">=</span> <span class="n">deleted_directories</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">deleted_files</span> <span class="o">=</span> <span class="n">deleted_files</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">error</span> <span class="o">=</span> <span class="n">error</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">exif_updated</span> <span class="o">=</span> <span class="n">exif_updated</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">exiftool_error</span> <span class="o">=</span> <span class="n">exiftool_error</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">exiftool_warning</span> <span class="o">=</span> <span class="n">exiftool_warning</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">exported</span> <span class="o">=</span> <span class="n">exported</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">exported_album</span> <span class="o">=</span> <span class="n">exported_album</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">skipped_album</span> <span class="o">=</span> <span class="n">skipped_album</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">metadata_changed</span> <span class="o">=</span> <span class="n">metadata_changed</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">missing</span> <span class="o">=</span> <span class="n">missing</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">missing_album</span> <span class="o">=</span> <span class="n">missing_album</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">new</span> <span class="o">=</span> <span class="n">new</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sidecar_exiftool_skipped</span> <span class="o">=</span> <span class="n">sidecar_exiftool_skipped</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sidecar_exiftool_written</span> <span class="o">=</span> <span class="n">sidecar_exiftool_written</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sidecar_json_skipped</span> <span class="o">=</span> <span class="n">sidecar_json_skipped</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sidecar_json_written</span> <span class="o">=</span> <span class="n">sidecar_json_written</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sidecar_xmp_skipped</span> <span class="o">=</span> <span class="n">sidecar_xmp_skipped</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sidecar_xmp_written</span> <span class="o">=</span> <span class="n">sidecar_xmp_written</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">skipped</span> <span class="o">=</span> <span class="n">skipped</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">skipped_album</span> <span class="o">=</span> <span class="n">skipped_album</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">to_touch</span> <span class="o">=</span> <span class="n">to_touch</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">touched</span> <span class="o">=</span> <span class="n">touched</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">updated</span> <span class="o">=</span> <span class="n">updated</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">xattr_skipped</span> <span class="o">=</span> <span class="n">xattr_skipped</span> <span class="ow">or</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">xattr_written</span> <span class="o">=</span> <span class="n">xattr_written</span> <span class="ow">or</span> <span class="p">[]</span>
<div class="viewcode-block" id="ExportResults.all_files"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportResults.all_files">[docs]</a> <span class="k">def</span> <span class="nf">all_files</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""return all filenames contained in results"""</span>
@@ -537,13 +541,15 @@
<span class="bp">self</span><span class="o">.</span><span class="n">exported_album</span> <span class="o">+=</span> <span class="n">other</span><span class="o">.</span><span class="n">exported_album</span>
<span class="bp">self</span><span class="o">.</span><span class="n">skipped_album</span> <span class="o">+=</span> <span class="n">other</span><span class="o">.</span><span class="n">skipped_album</span>
<span class="bp">self</span><span class="o">.</span><span class="n">missing_album</span> <span class="o">+=</span> <span class="n">other</span><span class="o">.</span><span class="n">missing_album</span>
<span class="bp">self</span><span class="o">.</span><span class="n">metadata_changed</span> <span class="o">+=</span> <span class="n">other</span><span class="o">.</span><span class="n">metadata_changed</span>
<span class="k">return</span> <span class="bp">self</span>
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="p">(</span>
<span class="s2">"ExportResults("</span>
<span class="o">+</span> <span class="sa">f</span><span class="s2">"exported=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">exported</span><span class="si">}</span><span class="s2">"</span>
<span class="o">+</span> <span class="sa">f</span><span class="s2">"datetime=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">datetime</span><span class="si">}</span><span class="s2">"</span>
<span class="o">+</span> <span class="sa">f</span><span class="s2">",exported=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">exported</span><span class="si">}</span><span class="s2">"</span>
<span class="o">+</span> <span class="sa">f</span><span class="s2">",new=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">new</span><span class="si">}</span><span class="s2">"</span>
<span class="o">+</span> <span class="sa">f</span><span class="s2">",updated=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">updated</span><span class="si">}</span><span class="s2">"</span>
<span class="o">+</span> <span class="sa">f</span><span class="s2">",skipped=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">skipped</span><span class="si">}</span><span class="s2">"</span>
@@ -566,12 +572,14 @@
<span class="o">+</span> <span class="sa">f</span><span class="s2">",exported_album=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">exported_album</span><span class="si">}</span><span class="s2">"</span>
<span class="o">+</span> <span class="sa">f</span><span class="s2">",skipped_album=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">skipped_album</span><span class="si">}</span><span class="s2">"</span>
<span class="o">+</span> <span class="sa">f</span><span class="s2">",missing_album=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">missing_album</span><span class="si">}</span><span class="s2">"</span>
<span class="o">+</span> <span class="sa">f</span><span class="s2">",metadata_changed=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">metadata_changed</span><span class="si">}</span><span class="s2">"</span>
<span class="o">+</span> <span class="s2">")"</span>
<span class="p">)</span></div>
<div class="viewcode-block" id="PhotoExporter"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoExporter">[docs]</a><span class="k">class</span> <span class="nc">PhotoExporter</span><span class="p">:</span>
<span class="sd">"""Export a photo"""</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">photo</span><span class="p">:</span> <span class="s2">"PhotoInfo"</span><span class="p">,</span> <span class="n">tmpdir</span><span class="p">:</span> <span class="n">t</span><span class="o">.</span><span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">photo</span> <span class="o">=</span> <span class="n">photo</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_render_options</span> <span class="o">=</span> <span class="n">RenderOptions</span><span class="p">()</span>
@@ -934,7 +942,7 @@
<span class="k">return</span> <span class="n">ShouldUpdate</span><span class="o">.</span><span class="n">EDITED_SIG_DIFFERENT</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">force_update</span><span class="p">:</span>
<span class="n">current_digest</span> <span class="o">=</span> <span class="n">hexdigest</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">json</span><span class="p">())</span>
<span class="n">current_digest</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">hexdigest</span>
<span class="k">if</span> <span class="n">current_digest</span> <span class="o">!=</span> <span class="n">file_record</span><span class="o">.</span><span class="n">digest</span><span class="p">:</span>
<span class="c1"># metadata in Photos changed, force update</span>
<span class="k">return</span> <span class="n">ShouldUpdate</span><span class="o">.</span><span class="n">DIGEST_DIFFERENT</span>
@@ -1374,8 +1382,9 @@
<span class="n">rec</span><span class="o">.</span><span class="n">dest_sig</span> <span class="o">=</span> <span class="n">fileutil</span><span class="o">.</span><span class="n">file_sig</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">exiftool</span><span class="p">:</span>
<span class="n">rec</span><span class="o">.</span><span class="n">exifdata</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_exiftool_json_sidecar</span><span class="p">(</span><span class="n">options</span><span class="p">)</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">force_update</span><span class="p">:</span>
<span class="n">rec</span><span class="o">.</span><span class="n">digest</span> <span class="o">=</span> <span class="n">hexdigest</span><span class="p">(</span><span class="n">photoinfo</span><span class="p">)</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">hexdigest</span> <span class="o">!=</span> <span class="n">rec</span><span class="o">.</span><span class="n">digest</span><span class="p">:</span>
<span class="n">results</span><span class="o">.</span><span class="n">metadata_changed</span> <span class="o">=</span> <span class="p">[</span><span class="n">dest_str</span><span class="p">]</span>
<span class="n">rec</span><span class="o">.</span><span class="n">digest</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">hexdigest</span>
<span class="k">return</span> <span class="n">results</span>
@@ -2206,13 +2215,6 @@
<span class="n">f</span><span class="o">.</span><span class="n">close</span><span class="p">()</span></div>
<span class="k">def</span> <span class="nf">hexdigest</span><span class="p">(</span><span class="n">strval</span><span class="p">):</span>
<span class="sd">"""hexdigest of a string, using blake2b"""</span>
<span class="n">h</span> <span class="o">=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">blake2b</span><span class="p">(</span><span class="n">digest_size</span><span class="o">=</span><span class="mi">20</span><span class="p">)</span>
<span class="n">h</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="nb">bytes</span><span class="p">(</span><span class="n">strval</span><span class="p">,</span> <span class="s2">"utf-8"</span><span class="p">))</span>
<span class="k">return</span> <span class="n">h</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">_check_export_suffix</span><span class="p">(</span><span class="n">src</span><span class="p">,</span> <span class="n">dest</span><span class="p">,</span> <span class="n">edited</span><span class="p">):</span>
<span class="sd">"""Helper function for exporting photos to check file extensions of destination path.</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.photoinfo - osxphotos 0.48.1 documentation</title>
<title>osxphotos.photoinfo - osxphotos 0.48.7 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.48.7 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.48.7 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -207,6 +207,7 @@
<span class="kn">import</span> <span class="nn">os.path</span>
<span class="kn">import</span> <span class="nn">pathlib</span>
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">timedelta</span><span class="p">,</span> <span class="n">timezone</span>
<span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">cached_property</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
<span class="kn">import</span> <span class="nn">yaml</span>
@@ -250,7 +251,7 @@
<span class="kn">from</span> <span class="nn">.searchinfo</span> <span class="kn">import</span> <span class="n">SearchInfo</span>
<span class="kn">from</span> <span class="nn">.text_detection</span> <span class="kn">import</span> <span class="n">detect_text</span>
<span class="kn">from</span> <span class="nn">.uti</span> <span class="kn">import</span> <span class="n">get_preferred_uti_extension</span><span class="p">,</span> <span class="n">get_uti_for_extension</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">_get_resource_loc</span><span class="p">,</span> <span class="n">list_directory</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">_get_resource_loc</span><span class="p">,</span> <span class="n">hexdigest</span><span class="p">,</span> <span class="n">list_directory</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"PhotoInfo"</span><span class="p">,</span> <span class="s2">"PhotoInfoNone"</span><span class="p">]</span>
@@ -1088,38 +1089,37 @@
<span class="k">return</span> <span class="n">photopath</span>
<span class="nd">@property</span>
<span class="nd">@cached_property</span>
<span class="k">def</span> <span class="nf">path_derivatives</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first)"""</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">_path_derivatives</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_version</span> <span class="o">&lt;=</span> <span class="n">_PHOTOS_4_VERSION</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_path_derivatives</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_path_derivatives_4</span><span class="p">()</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_path_derivatives</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_version</span> <span class="o">&lt;=</span> <span class="n">_PHOTOS_4_VERSION</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_path_derivatives_4</span><span class="p">()</span>
<span class="n">directory</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_uuid</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="c1"># first char of uuid</span>
<span class="n">derivative_path</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_library_path</span><span class="p">)</span>
<span class="o">/</span> <span class="s2">"resources"</span>
<span class="o">/</span> <span class="s2">"derivatives"</span>
<span class="o">/</span> <span class="n">directory</span>
<span class="p">)</span>
<span class="n">files</span> <span class="o">=</span> <span class="n">derivative_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</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">*.*"</span><span class="p">)</span>
<span class="n">files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">files</span><span class="p">,</span> <span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">f</span><span class="p">:</span> <span class="n">f</span><span class="o">.</span><span class="n">stat</span><span class="p">()</span><span class="o">.</span><span class="n">st_size</span><span class="p">)</span>
<span class="c1"># return list of filename but skip .THM files (these are actually low-res thumbnails in JPEG format but with .THM extension)</span>
<span class="n">derivatives</span> <span class="o">=</span> <span class="p">[</span>
<span class="nb">str</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">files</span> <span class="k">if</span> <span class="n">filename</span><span class="o">.</span><span class="n">suffix</span> <span class="o">!=</span> <span class="s2">".THM"</span>
<span class="p">]</span>
<span class="k">if</span> <span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">isphoto</span>
<span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">derivatives</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span>
<span class="ow">and</span> <span class="n">derivatives</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">".mov"</span><span class="p">)</span>
<span class="p">):</span>
<span class="n">derivatives</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">derivatives</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">derivatives</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">derivatives</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">shared</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_path_derivatives_5_shared</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_path_derivatives</span> <span class="o">=</span> <span class="n">derivatives</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_path_derivatives</span>
<span class="n">directory</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_uuid</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="c1"># first char of uuid</span>
<span class="n">derivative_path</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_library_path</span><span class="p">)</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">"resources/derivatives/</span><span class="si">{</span><span class="n">directory</span><span class="si">}</span><span class="s2">"</span>
<span class="p">)</span>
<span class="n">files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">derivative_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</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">*.*"</span><span class="p">))</span>
<span class="c1"># previews may be missing from derivatives path</span>
<span class="c1"># there are what appear to be low res thumbnails in the "masters" subfolder</span>
<span class="n">thumb_path</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_library_path</span><span class="p">)</span>
<span class="o">/</span> <span class="sa">f</span><span class="s2">"resources/derivatives/masters/</span><span class="si">{</span><span class="n">directory</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="si">}</span><span class="s2">_4_5005_c.jpeg"</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">thumb_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
<span class="n">files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">thumb_path</span><span class="p">)</span>
<span class="n">files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">files</span><span class="p">,</span> <span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">f</span><span class="p">:</span> <span class="n">f</span><span class="o">.</span><span class="n">stat</span><span class="p">()</span><span class="o">.</span><span class="n">st_size</span><span class="p">)</span>
<span class="c1"># return list of filename but skip .THM files (these are actually low-res thumbnails in JPEG format but with .THM extension)</span>
<span class="n">derivatives</span> <span class="o">=</span> <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">files</span> <span class="k">if</span> <span class="n">filename</span><span class="o">.</span><span class="n">suffix</span> <span class="o">!=</span> <span class="s2">".THM"</span><span class="p">]</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">isphoto</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">derivatives</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="ow">and</span> <span class="n">derivatives</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">".mov"</span><span class="p">):</span>
<span class="n">derivatives</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">derivatives</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">derivatives</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">derivatives</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="k">return</span> <span class="n">derivatives</span>
<span class="k">def</span> <span class="nf">_path_derivatives_4</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""Return paths to all derivative (preview) files for Photos &lt;= 4"""</span>
@@ -1129,10 +1129,7 @@
<span class="n">folder_id</span><span class="p">,</span> <span class="n">file_id</span> <span class="o">=</span> <span class="n">_get_resource_loc</span><span class="p">(</span><span class="n">modelid</span><span class="p">)</span>
<span class="n">derivatives_root</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_library_path</span><span class="p">)</span>
<span class="o">/</span> <span class="s2">"resources"</span>
<span class="o">/</span> <span class="s2">"proxies"</span>
<span class="o">/</span> <span class="s2">"derivatives"</span>
<span class="o">/</span> <span class="n">folder_id</span>
<span class="o">/</span> <span class="sa">f</span><span class="s2">"resources/proxies/derivatives/</span><span class="si">{</span><span class="n">folder_id</span><span class="si">}</span><span class="s2">"</span>
<span class="p">)</span>
<span class="c1"># photos appears to usually be in "00" subfolder but</span>
@@ -1157,6 +1154,19 @@
<span class="c1"># didn't find a derivatives path</span>
<span class="k">return</span> <span class="p">[]</span>
<span class="k">def</span> <span class="nf">_path_derivatives_5_shared</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""Return paths to all derivative (preview) files for shared iCloud photos in Photos &gt;= 5"""</span>
<span class="n">directory</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_uuid</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="c1"># first char of uuid</span>
<span class="c1"># only 1 derivative for shared photos and it's called 'UUID_4_5005_c.jpeg'</span>
<span class="n">derivative_path</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_library_path</span><span class="p">)</span>
<span class="o">/</span> <span class="s2">"resources/cloudsharing/resources/derivatives/masters"</span>
<span class="o">/</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">directory</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="si">}</span><span class="s2">_4_5005_c.jpeg"</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">derivative_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
<span class="k">return</span> <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">derivative_path</span><span class="p">)]</span>
<span class="k">return</span> <span class="p">[]</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">panorama</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""Returns True if photo is a panorama, otherwise False"""</span>
@@ -1552,6 +1562,12 @@
<span class="bp">self</span><span class="o">.</span><span class="n">_exiftool</span> <span class="o">=</span> <span class="n">exiftool</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_exiftool</span>
<span class="nd">@cached_property</span>
<span class="k">def</span> <span class="nf">hexdigest</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""Returns a unique digest of the photo's properties and metadata;</span>
<span class="sd"> useful for detecting changes in any property/metadata of the photo"""</span>
<span class="k">return</span> <span class="n">hexdigest</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">json</span><span class="p">())</span>
<div class="viewcode-block" id="PhotoInfo.detected_text"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoInfo.detected_text">[docs]</a> <span class="k">def</span> <span class="nf">detected_text</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">confidence_threshold</span><span class="o">=</span><span class="n">TEXT_DETECTION_CONFIDENCE_THRESHOLD</span><span class="p">):</span>
<span class="sd">"""Detects text in photo and returns lists of results as (detected text, confidence)</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../../genindex.html" /><link rel="search" title="Search" href="../../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.photosdb.photosdb - osxphotos 0.48.1 documentation</title>
<title>osxphotos.photosdb.photosdb - osxphotos 0.48.7 documentation</title>
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../../index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
<a href="../../../index.html"><div class="brand">osxphotos 0.48.7 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../../index.html">
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.48.7 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -3272,6 +3272,8 @@
<span class="n">photos</span> <span class="o">=</span> <span class="n">_get_photos_by_attribute</span><span class="p">(</span>
<span class="n">photos</span><span class="p">,</span> <span class="s2">"keywords"</span><span class="p">,</span> <span class="n">keyword</span><span class="p">,</span> <span class="n">options</span><span class="o">.</span><span class="n">ignore_case</span>
<span class="p">)</span>
<span class="k">elif</span> <span class="n">options</span><span class="o">.</span><span class="n">no_keyword</span><span class="p">:</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">p</span><span class="o">.</span><span class="n">keywords</span><span class="p">]</span>
<span class="k">if</span> <span class="n">person</span><span class="p">:</span>
<span class="n">photos</span> <span class="o">=</span> <span class="n">_get_photos_by_attribute</span><span class="p">(</span>
@@ -3642,6 +3644,23 @@
<span class="n">matching_photos</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">p</span><span class="p">)</span>
<span class="n">photos</span> <span class="o">=</span> <span class="n">matching_photos</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">added_after</span><span class="p">:</span>
<span class="n">added_after</span> <span class="o">=</span> <span class="n">options</span><span class="o">.</span><span class="n">added_after</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">datetime_has_tz</span><span class="p">(</span><span class="n">added_after</span><span class="p">):</span>
<span class="n">added_after</span> <span class="o">=</span> <span class="n">datetime_naive_to_local</span><span class="p">(</span><span class="n">added_after</span><span class="p">)</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">date_added</span> <span class="ow">and</span> <span class="n">p</span><span class="o">.</span><span class="n">date_added</span> <span class="o">&gt;</span> <span class="n">added_after</span><span class="p">]</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">added_before</span><span class="p">:</span>
<span class="n">added_before</span> <span class="o">=</span> <span class="n">options</span><span class="o">.</span><span class="n">added_before</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">datetime_has_tz</span><span class="p">(</span><span class="n">added_before</span><span class="p">):</span>
<span class="n">added_before</span> <span class="o">=</span> <span class="n">datetime_naive_to_local</span><span class="p">(</span><span class="n">added_before</span><span class="p">)</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">date_added</span> <span class="ow">and</span> <span class="n">p</span><span class="o">.</span><span class="n">date_added</span> <span class="o">&lt;</span> <span class="n">added_before</span><span class="p">]</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">added_in_last</span><span class="p">:</span>
<span class="n">added_after</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span> <span class="o">-</span> <span class="n">options</span><span class="o">.</span><span class="n">added_in_last</span>
<span class="n">added_after</span> <span class="o">=</span> <span class="n">datetime_naive_to_local</span><span class="p">(</span><span class="n">added_after</span><span class="p">)</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">date_added</span> <span class="ow">and</span> <span class="n">p</span><span class="o">.</span><span class="n">date_added</span> <span class="o">&gt;</span> <span class="n">added_after</span><span class="p">]</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">function</span><span class="p">:</span>
<span class="k">for</span> <span class="n">function</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">function</span><span class="p">:</span>
<span class="n">photos</span> <span class="o">=</span> <span class="n">function</span><span class="p">[</span><span class="mi">0</span><span class="p">](</span><span class="n">photos</span><span class="p">)</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.phototemplate - osxphotos 0.48.1 documentation</title>
<title>osxphotos.phototemplate - osxphotos 0.49.0 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.49.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -196,14 +196,14 @@
<h1>Source code for osxphotos.phototemplate</h1><div class="highlight"><pre>
<span></span><span class="sd">""" Custom template system for osxphotos, implements metadata template language (MTL) """</span>
<span class="kn">import</span> <span class="nn">datetime</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">locale</span>
<span class="kn">import</span> <span class="nn">logging</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">pathlib</span>
<span class="kn">import</span> <span class="nn">shlex</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">suppress</span>
<span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
@@ -615,7 +615,7 @@
<span class="k">try</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">template</span><span class="p">)</span>
<span class="k">except</span> <span class="n">TextXSyntaxError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"SyntaxError: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"SyntaxError: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">model</span><span class="p">:</span>
<span class="c1"># empty string</span>
@@ -808,10 +808,12 @@
<span class="k">break</span>
<span class="k">if</span> <span class="n">match</span><span class="p">:</span>
<span class="k">break</span>
<span class="k">if</span> <span class="p">(</span><span class="n">match</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">negation</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">negation</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">match</span><span class="p">):</span>
<span class="k">return</span> <span class="p">[</span><span class="s2">"True"</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="p">[]</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p">[</span><span class="s2">"True"</span><span class="p">]</span>
<span class="k">if</span> <span class="p">(</span><span class="n">match</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">negation</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">negation</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">match</span><span class="p">)</span>
<span class="k">else</span> <span class="p">[]</span>
<span class="p">)</span>
<span class="k">def</span> <span class="nf">comparison_test</span><span class="p">(</span><span class="n">test_function</span><span class="p">):</span>
<span class="sd">"""Perform numerical comparisons using test_function; closure to capture conditional_val, vals, negation"""</span>
@@ -823,14 +825,16 @@
<span class="n">match</span> <span class="o">=</span> <span class="nb">bool</span><span class="p">(</span>
<span class="n">test_function</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">vals</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span> <span class="nb">float</span><span class="p">(</span><span class="n">conditional_value</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span>
<span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="n">match</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">negation</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">negation</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">match</span><span class="p">):</span>
<span class="k">return</span> <span class="p">[</span><span class="s2">"True"</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="p">[]</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p">[</span><span class="s2">"True"</span><span class="p">]</span>
<span class="k">if</span> <span class="p">(</span><span class="n">match</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">negation</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">negation</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">match</span><span class="p">)</span>
<span class="k">else</span> <span class="p">[]</span>
<span class="p">)</span>
<span class="k">except</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">"comparison operators may only be used with values that can be converted to numbers: </span><span class="si">{</span><span class="n">vals</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">conditional_value</span><span class="si">}</span><span class="s2">"</span>
<span class="p">)</span>
<span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
<span class="k">if</span> <span class="n">operator</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"contains"</span><span class="p">,</span> <span class="s2">"matches"</span><span class="p">,</span> <span class="s2">"startswith"</span><span class="p">,</span> <span class="s2">"endswith"</span><span class="p">]:</span>
<span class="c1"># process any "or" values separated by "|"</span>
@@ -918,9 +922,6 @@
<span class="sd"> ValueError if no rule exists for field.</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">return</span> <span class="p">[]</span>
<span class="c1"># initialize today with current date/time if needed</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">today</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">today</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
@@ -928,7 +929,50 @@
<span class="n">value</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># wouldn't a switch/case statement be nice...</span>
<span class="k">if</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"name"</span><span class="p">:</span>
<span class="c1"># handle the fields that don't require a PhotoInfo object first</span>
<span class="k">if</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.date"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">date</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.year"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">year</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.yy"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">yy</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.mm"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">mm</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.month"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">month</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.mon"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">mon</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.dd"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">dd</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.dow"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">dow</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.doy"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">doy</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.hour"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">hour</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.min"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">min</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.sec"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">sec</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.strftime"</span><span class="p">:</span>
<span class="k">if</span> <span class="n">default</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="n">default</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="k">except</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Invalid strftime template: '</span><span class="si">{</span><span class="n">default</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">elif</span> <span class="n">field</span> <span class="ow">in</span> <span class="n">PUNCTUATION</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">PUNCTUATION</span><span class="p">[</span><span class="n">field</span><span class="p">]</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"osxphotos_version"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">__version__</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"osxphotos_cmd_line"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="s2">" "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span>
<span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="c1"># if no uuid, don't have a PhotoInfo object (could be PhotoInfoNone)</span>
<span class="c1"># so don't try to handle any of the photo fields</span>
<span class="k">return</span> <span class="p">[]</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"name"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">filename</span><span class="p">)</span><span class="o">.</span><span class="n">stem</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"original_name"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">original_filename</span><span class="p">)</span><span class="o">.</span><span class="n">stem</span>
@@ -1061,38 +1105,6 @@
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Invalid strftime template: '</span><span class="si">{</span><span class="n">default</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.date"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">date</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.year"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">year</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.yy"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">yy</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.mm"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">mm</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.month"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">month</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.mon"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">mon</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.dd"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">dd</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.dow"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">dow</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.doy"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">doy</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.hour"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">hour</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.min"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">min</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.sec"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">DateTimeFormatter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="p">)</span><span class="o">.</span><span class="n">sec</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"today.strftime"</span><span class="p">:</span>
<span class="k">if</span> <span class="n">default</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">today</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="n">default</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="k">except</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Invalid strftime template: '</span><span class="si">{</span><span class="n">default</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"place.name"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">place</span><span class="o">.</span><span class="n">name</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">place</span> <span class="k">else</span> <span class="kc">None</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"place.country_code"</span><span class="p">:</span>
@@ -1178,33 +1190,25 @@
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"id"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">format_str_value</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">"pk"</span><span class="p">],</span> <span class="n">subfield</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">field</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"album_seq"</span><span class="p">)</span> <span class="ow">or</span> <span class="n">field</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"folder_album_seq"</span><span class="p">):</span>
<span class="n">dest_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">dest_path</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">dest_path</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">if</span> <span class="n">dest_path</span> <span class="o">:=</span> <span class="bp">self</span><span class="o">.</span><span class="n">dest_path</span><span class="p">:</span>
<span class="k">if</span> <span class="n">field</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"album_seq"</span><span class="p">):</span>
<span class="n">album</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">dest_path</span><span class="p">)</span><span class="o">.</span><span class="n">name</span>
<span class="n">album_info</span> <span class="o">=</span> <span class="n">_get_album_by_name</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="p">,</span> <span class="n">album</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">album_info</span> <span class="o">=</span> <span class="n">_get_album_by_path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="p">,</span> <span class="n">dest_path</span><span class="p">)</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">album_info</span><span class="o">.</span><span class="n">photo_index</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="p">)</span> <span class="k">if</span> <span class="n">album_info</span> <span class="k">else</span> <span class="kc">None</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">with</span> <span class="n">suppress</span><span class="p">(</span><span class="ne">IndexError</span><span class="p">):</span>
<span class="n">start_id</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">"."</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">value</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">+</span> <span class="nb">int</span><span class="p">(</span><span class="n">start_id</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="k">except</span> <span class="ne">IndexError</span><span class="p">:</span>
<span class="k">pass</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">format_str_value</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">subfield</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">field</span> <span class="ow">in</span> <span class="n">PUNCTUATION</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">PUNCTUATION</span><span class="p">[</span><span class="n">field</span><span class="p">]</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"osxphotos_version"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">__version__</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"osxphotos_cmd_line"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="s2">" "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># if here, didn't get a match</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Unhandled template value: </span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="c1"># sanitize filename or directory name if needed</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">filename</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">sanitize_pathpart</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">dirname</span><span class="p">:</span>
@@ -1234,8 +1238,8 @@
<span class="n">field_value</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">field_value</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field_stem</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Unknown path-like field: </span><span class="si">{</span><span class="n">field_stem</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">AttributeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Unknown path-like field: </span><span class="si">{</span><span class="n">field_stem</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">_get_pathlib_value</span><span class="p">(</span><span class="n">field</span><span class="p">,</span> <span class="n">field_value</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">quote</span><span class="p">)</span>
@@ -1279,14 +1283,14 @@
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"{"</span> <span class="o">+</span> <span class="n">values</span> <span class="o">+</span> <span class="s2">"}"</span><span class="p">]</span> <span class="k">if</span> <span class="n">values</span> <span class="k">else</span> <span class="p">[]</span>
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"parens"</span><span class="p">:</span>
<span class="k">if</span> <span class="n">values</span> <span class="ow">and</span> <span class="nb">type</span><span class="p">(</span><span class="n">values</span><span class="p">)</span> <span class="o">==</span> <span class="nb">list</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"("</span> <span class="o">+</span> <span class="n">v</span> <span class="o">+</span> <span class="s2">")"</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span><span class="p">]</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">"(</span><span class="si">{</span><span class="n">v</span><span class="si">}</span><span class="s2">)"</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"("</span> <span class="o">+</span> <span class="n">values</span> <span class="o">+</span> <span class="s2">")"</span><span class="p">]</span> <span class="k">if</span> <span class="n">values</span> <span class="k">else</span> <span class="p">[]</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">"(</span><span class="si">{</span><span class="n">values</span><span class="si">}</span><span class="s2">)"</span><span class="p">]</span> <span class="k">if</span> <span class="n">values</span> <span class="k">else</span> <span class="p">[]</span>
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"brackets"</span><span class="p">:</span>
<span class="k">if</span> <span class="n">values</span> <span class="ow">and</span> <span class="nb">type</span><span class="p">(</span><span class="n">values</span><span class="p">)</span> <span class="o">==</span> <span class="nb">list</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"["</span> <span class="o">+</span> <span class="n">v</span> <span class="o">+</span> <span class="s2">"]"</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span><span class="p">]</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">"[</span><span class="si">{</span><span class="n">v</span><span class="si">}</span><span class="s2">]"</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"["</span> <span class="o">+</span> <span class="n">values</span> <span class="o">+</span> <span class="s2">"]"</span><span class="p">]</span> <span class="k">if</span> <span class="n">values</span> <span class="k">else</span> <span class="p">[]</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">"[</span><span class="si">{</span><span class="n">values</span><span class="si">}</span><span class="s2">]"</span><span class="p">]</span> <span class="k">if</span> <span class="n">values</span> <span class="k">else</span> <span class="p">[]</span>
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"shell_quote"</span><span class="p">:</span>
<span class="k">if</span> <span class="n">values</span> <span class="ow">and</span> <span class="nb">type</span><span class="p">(</span><span class="n">values</span><span class="p">)</span> <span class="o">==</span> <span class="nb">list</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">shlex</span><span class="o">.</span><span class="n">quote</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span><span class="p">]</span>
@@ -1602,7 +1606,7 @@
<span class="o">*</span><span class="n">TEMPLATE_SUBSTITUTIONS_MULTI_VALUED</span><span class="o">.</span><span class="n">items</span><span class="p">(),</span>
<span class="p">]:</span>
<span class="c1"># replace '|' with '\|' to avoid markdown parsing issues (e.g. in {pipe} description)</span>
<span class="n">descr</span> <span class="o">=</span> <span class="n">descr</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"'|'"</span><span class="p">,</span> <span class="s2">"'\|'"</span><span class="p">)</span>
<span class="n">descr</span> <span class="o">=</span> <span class="n">descr</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"'|'"</span><span class="p">,</span> <span class="sa">r</span><span class="s2">"'\|'"</span><span class="p">)</span>
<span class="n">template_table</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">"</span><span class="se">\n</span><span class="s2">|</span><span class="si">{</span><span class="n">subst</span><span class="si">}</span><span class="s2">|</span><span class="si">{</span><span class="n">descr</span><span class="si">}</span><span class="s2">|"</span>
<span class="k">return</span> <span class="n">template_table</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.queryoptions - osxphotos 0.47.9 documentation</title>
<title>osxphotos.queryoptions - osxphotos 0.48.7 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.47.9 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.48.7 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.47.9 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.48.7 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -155,11 +155,12 @@
</form>
<div id="searchbox"></div><div class="sidebar-scroll"><div class="sidebar-tree">
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../overview.html">osxphotos</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../tutorial.html">osxphotos Tutorial</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../cli.html">osxphotos Command Line Interface (CLI)</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../package_overview.html">Example uses of the python package</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../reference.html">osxphotos python API</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../overview.html">OSXPhotos</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../tutorial.html">OSXPhotos Tutorial</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../cli.html">OSXPhotos Command Line Interface (CLI)</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../template_help.html">OSXPhotos Template System</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../package_overview.html">OSXPhotos Python Package Overview</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../reference.html">OSXPhotos python API</a></li>
</ul>
</div>
@@ -210,158 +211,165 @@
<span class="sd">"""QueryOptions class for PhotosDB.query</span>
<span class="sd"> Attributes:</span>
<span class="sd"> keyword: list of keywords to search for</span>
<span class="sd"> person: list of person names to search for</span>
<span class="sd"> added_after: search for photos added after a given date</span>
<span class="sd"> added_before: search for photos added before a given date</span>
<span class="sd"> added_in_last: search for photos added in last X datetime.timedelta</span>
<span class="sd"> album: list of album names to search for</span>
<span class="sd"> folder: list of folder names to search for</span>
<span class="sd"> uuid: list of uuids to search for</span>
<span class="sd"> title: list of titles to search for</span>
<span class="sd"> no_title: search for photos with no title</span>
<span class="sd"> burst_photos: search for burst photos</span>
<span class="sd"> burst: search for burst photos</span>
<span class="sd"> cloudasset: search for photos that are managed by iCloud</span>
<span class="sd"> deleted_only: search only for deleted photos</span>
<span class="sd"> deleted: also include deleted photos</span>
<span class="sd"> description: list of descriptions to search for</span>
<span class="sd"> no_description: search for photos with no description</span>
<span class="sd"> ignore_case: ignore case when searching</span>
<span class="sd"> duplicate: search for duplicate photos</span>
<span class="sd"> edited: search for edited photos</span>
<span class="sd"> exif: search for photos with EXIF tags that matches the given data</span>
<span class="sd"> external_edit: search for photos edited in external apps</span>
<span class="sd"> favorite: search for favorite photos</span>
<span class="sd"> not_favorite: search for non-favorite photos</span>
<span class="sd"> hidden: search for hidden photos</span>
<span class="sd"> not_hidden: search for non-hidden photos</span>
<span class="sd"> missing: search for missing photos</span>
<span class="sd"> not_missing: search for non-missing photos</span>
<span class="sd"> shared: search for shared photos</span>
<span class="sd"> not_shared: search for non-shared photos</span>
<span class="sd"> photos: search for photos</span>
<span class="sd"> movies: search for movies</span>
<span class="sd"> uti: list of UTIs to search for</span>
<span class="sd"> burst: search for burst photos</span>
<span class="sd"> not_burst: search for non-burst photos</span>
<span class="sd"> live: search for live photos</span>
<span class="sd"> not_live: search for non-live photos</span>
<span class="sd"> cloudasset: search for photos that are managed by iCloud</span>
<span class="sd"> not_cloudasset: search for photos that are not managed by iCloud</span>
<span class="sd"> incloud: search for cloud assets that are synched to iCloud</span>
<span class="sd"> not_incloud: search for cloud asset photos that are not yet synched to iCloud</span>
<span class="sd"> folder: list of folder names to search for</span>
<span class="sd"> from_date: search for photos taken on or after this date</span>
<span class="sd"> to_date: search for photos taken on or before this date</span>
<span class="sd"> portrait: search for portrait photos</span>
<span class="sd"> not_portrait: search for non-portrait photos</span>
<span class="sd"> screenshot: search for screenshot photos</span>
<span class="sd"> not_screenshot: search for non-screenshot photos</span>
<span class="sd"> slow_mo: search for slow-mo photos</span>
<span class="sd"> not_slow_mo: search for non-slow-mo photos</span>
<span class="sd"> time_lapse: search for time-lapse photos</span>
<span class="sd"> not_time_lapse: search for non-time-lapse photos</span>
<span class="sd"> hdr: search for HDR photos</span>
<span class="sd"> not_hdr: search for non-HDR photos</span>
<span class="sd"> selfie: search for selfie photos</span>
<span class="sd"> not_selfie: search for non-selfie photos</span>
<span class="sd"> panorama: search for panorama photos</span>
<span class="sd"> not_panorama: search for non-panorama photos</span>
<span class="sd"> has_raw: search for photos with associated raw files</span>
<span class="sd"> place: list of place names to search for</span>
<span class="sd"> no_place: search for photos with no place</span>
<span class="sd"> label: list of labels to search for</span>
<span class="sd"> deleted: also include deleted photos</span>
<span class="sd"> deleted_only: search only for deleted photos</span>
<span class="sd"> has_comment: search for photos with comments</span>
<span class="sd"> no_comment: search for photos with no comments</span>
<span class="sd"> has_likes: search for shared photos with likes</span>
<span class="sd"> no_likes: search for shared photos with no likes</span>
<span class="sd"> is_reference: search for photos stored by reference (that is, they are not managed by Photos)</span>
<span class="sd"> in_album: search for photos in an album</span>
<span class="sd"> not_in_album: search for photos not in an album</span>
<span class="sd"> burst_photos: search for burst photos</span>
<span class="sd"> missing_bursts: for burst photos, also include burst photos that are missing</span>
<span class="sd"> name: list of names to search for</span>
<span class="sd"> min_size: minimum size of photos to search for</span>
<span class="sd"> max_size: maximum size of photos to search for</span>
<span class="sd"> regex: list of regular expressions to search for</span>
<span class="sd"> query_eval: list of query expressions to evaluate</span>
<span class="sd"> duplicate: search for duplicate photos</span>
<span class="sd"> location: search for photos with a location</span>
<span class="sd"> no_location: search for photos with no location</span>
<span class="sd"> function: list of query functions to evaluate</span>
<span class="sd"> has_comment: search for photos with comments</span>
<span class="sd"> has_likes: search for shared photos with likes</span>
<span class="sd"> has_raw: search for photos with associated raw files</span>
<span class="sd"> hdr: search for HDR photos</span>
<span class="sd"> hidden: search for hidden photos</span>
<span class="sd"> ignore_case: ignore case when searching</span>
<span class="sd"> in_album: search for photos in an album</span>
<span class="sd"> incloud: search for cloud assets that are synched to iCloud</span>
<span class="sd"> is_reference: search for photos stored by reference (that is, they are not managed by Photos)</span>
<span class="sd"> keyword: list of keywords to search for</span>
<span class="sd"> label: list of labels to search for</span>
<span class="sd"> live: search for live photos</span>
<span class="sd"> location: search for photos with a location</span>
<span class="sd"> max_size: maximum size of photos to search for</span>
<span class="sd"> min_size: minimum size of photos to search for</span>
<span class="sd"> missing_bursts: for burst photos, also include burst photos that are missing</span>
<span class="sd"> missing: search for missing photos</span>
<span class="sd"> movies: search for movies</span>
<span class="sd"> name: list of names to search for</span>
<span class="sd"> no_comment: search for photos with no comments</span>
<span class="sd"> no_description: search for photos with no description</span>
<span class="sd"> no_likes: search for shared photos with no likes</span>
<span class="sd"> no_location: search for photos with no location</span>
<span class="sd"> no_keyword: search for photos with no keywords</span>
<span class="sd"> no_place: search for photos with no place</span>
<span class="sd"> no_title: search for photos with no title</span>
<span class="sd"> not_burst: search for non-burst photos</span>
<span class="sd"> not_cloudasset: search for photos that are not managed by iCloud</span>
<span class="sd"> not_favorite: search for non-favorite photos</span>
<span class="sd"> not_hdr: search for non-HDR photos</span>
<span class="sd"> not_hidden: search for non-hidden photos</span>
<span class="sd"> not_in_album: search for photos not in an album</span>
<span class="sd"> not_incloud: search for cloud asset photos that are not yet synched to iCloud</span>
<span class="sd"> not_live: search for non-live photos</span>
<span class="sd"> not_missing: search for non-missing photos</span>
<span class="sd"> not_panorama: search for non-panorama photos</span>
<span class="sd"> not_portrait: search for non-portrait photos</span>
<span class="sd"> not_screenshot: search for non-screenshot photos</span>
<span class="sd"> not_selfie: search for non-selfie photos</span>
<span class="sd"> not_shared: search for non-shared photos</span>
<span class="sd"> not_slow_mo: search for non-slow-mo photos</span>
<span class="sd"> not_time_lapse: search for non-time-lapse photos</span>
<span class="sd"> panorama: search for panorama photos</span>
<span class="sd"> person: list of person names to search for</span>
<span class="sd"> photos: search for photos</span>
<span class="sd"> place: list of place names to search for</span>
<span class="sd"> portrait: search for portrait photos</span>
<span class="sd"> query_eval: list of query expressions to evaluate</span>
<span class="sd"> regex: list of regular expressions to search for</span>
<span class="sd"> screenshot: search for screenshot photos</span>
<span class="sd"> selected: search for selected photos</span>
<span class="sd"> exif: search for photos with EXIF tags that matches the given data</span>
<span class="sd"> selfie: search for selfie photos</span>
<span class="sd"> shared: search for shared photos</span>
<span class="sd"> slow_mo: search for slow-mo photos</span>
<span class="sd"> time_lapse: search for time-lapse photos</span>
<span class="sd"> title: list of titles to search for</span>
<span class="sd"> to_date: search for photos taken on or before this date</span>
<span class="sd"> uti: list of UTIs to search for</span>
<span class="sd"> uuid: list of uuids to search for</span>
<span class="sd"> year: search for photos taken in a given year</span>
<span class="sd"> """</span>
<span class="n">keyword</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">person</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">added_after</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">added_before</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">added_in_last</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="o">.</span><span class="n">timedelta</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">album</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">folder</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">uuid</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">title</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">no_title</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">burst_photos</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">burst</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">cloudasset</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">deleted_only</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">deleted</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">description</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">no_description</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">ignore_case</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">duplicate</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">edited</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">exif</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">external_edit</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">favorite</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_favorite</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">hidden</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_hidden</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">missing</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_missing</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">shared</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_shared</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">photos</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">movies</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">uti</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">burst</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_burst</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">live</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_live</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">cloudasset</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_cloudasset</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">incloud</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_incloud</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">folder</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">from_date</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">to_date</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">from_time</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="o">.</span><span class="n">time</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">to_time</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="o">.</span><span class="n">time</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">portrait</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_portrait</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">screenshot</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_screenshot</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">slow_mo</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_slow_mo</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">time_lapse</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_time_lapse</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">hdr</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_hdr</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">selfie</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_selfie</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">panorama</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_panorama</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">has_raw</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">place</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">no_place</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">label</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">deleted</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">deleted_only</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">has_comment</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">no_comment</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">has_likes</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">no_likes</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">is_reference</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">in_album</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_in_album</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">burst_photos</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">missing_bursts</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">name</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">min_size</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">bitmath</span><span class="o">.</span><span class="n">Byte</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">max_size</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">bitmath</span><span class="o">.</span><span class="n">Byte</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">regex</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">query_eval</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">duplicate</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">location</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">no_location</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">function</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="n">callable</span><span class="p">,</span> <span class="nb">str</span><span class="p">]]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">has_comment</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">has_likes</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">has_raw</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">hdr</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">hidden</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">ignore_case</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">in_album</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">incloud</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">is_reference</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">keyword</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">label</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">live</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">location</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">max_size</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">bitmath</span><span class="o">.</span><span class="n">Byte</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">min_size</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">bitmath</span><span class="o">.</span><span class="n">Byte</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">missing_bursts</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">missing</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">movies</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">name</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">no_comment</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">no_description</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">no_likes</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">no_location</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">no_keyword</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">no_place</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">no_title</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_burst</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_cloudasset</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_favorite</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_hdr</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_hidden</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_in_album</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_incloud</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_live</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_missing</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_panorama</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_portrait</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_screenshot</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_selfie</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_shared</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_slow_mo</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_time_lapse</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">panorama</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">person</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">photos</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">place</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">portrait</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">query_eval</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">regex</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">screenshot</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">selected</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">exif</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">selfie</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">shared</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">slow_mo</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">time_lapse</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">title</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">to_date</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">to_time</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="o">.</span><span class="n">time</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">uti</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">uuid</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">year</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">int</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>

View File

@@ -320,7 +320,7 @@ Template Substitutions
* - {crlf}
- a carriage return + line feed: '\r\n'
* - {osxphotos_version}
- The osxphotos version, e.g. '0.48.2'
- The osxphotos version, e.g. '0.49.1'
* - {osxphotos_cmd_line}
- The full command line used to run osxphotos
* - {album}

View File

@@ -195,7 +195,7 @@ Photos tracks a tremendous amount of metadata associated with photos in the libr
``osxphotos export /path/to/export --exiftool``
This will write basic metadata such as keywords, persons, and GPS location to the exported files. osxphotos includes several additional options that can be used in conjunction with ``--exiftool`` to modify the metadata that is written by ``exiftool``. For example, you can use the ``--keyword-template`` option to specify custom keywords (again, via the osxphotos template system). For example, to use the folder and album a photo is in to create hierarchal keywords in the format used by Lightroom Classic:
This will write basic metadata such as keywords, persons, and GPS location to the exported files. osxphotos includes several additional options that can be used in conjunction with ``--exiftool`` to modify the metadata that is written by ``exiftool``. For example, you can use the ``--keyword-template`` option to specify custom keywords (again, via the osxphotos template system). For example, to use the folder and album a photo is in to create hierarchical keywords in the format used by Lightroom Classic:
.. code-block::
@@ -209,7 +209,7 @@ This will write basic metadata such as keywords, persons, and GPS location to th
for joining the folders and albums. For example,
if photo is in Folder1/Folder2/Album, (>) produces
"Folder1>Folder2>Album" which some programs, such as
Lightroom Classic, treat as hierarchal keywords
Lightroom Classic, treat as hierarchical keywords
The above command will write all the regular metadata that ``--exiftool`` normally writes to the file upon export but will also add an additional keyword in the exported metadata in the form "Folder1>Folder2>Album". If you did not include the ``(>)`` in the template string (e.g. ``{folder_album}``\ ), folder_album would render in form "Folder1/Folder2/Album".
@@ -297,6 +297,10 @@ You can also export photos in a certain date range:
``osxphotos export /path/to/export --from-date "2020-01-01" --to-date "2020-02-28"``
or photos added to the library in the last week:
``osxphotos export /path/to/export --added-in-last "1 week"``
Converting images to JPEG on export
-----------------------------------

View File

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

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Template System" href="template_help.html" /><link rel="prev" title="OSXPhotos Tutorial" href="tutorial.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>OSXPhotos Command Line Interface (CLI) - osxphotos 0.48.2 documentation</title>
<title>OSXPhotos Command Line Interface (CLI) - osxphotos 0.49.1 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.48.2 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.49.1 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.48.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.1 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -385,6 +385,11 @@ to modify this behavior.</p>
<dd><p>Search for photos with keyword KEYWORD. If more than one keyword, treated as “OR”, e.g. find photos matching any keyword</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-no-keyword">
<span class="sig-name descname"><span class="pre">--no-keyword</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-export-no-keyword" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos with no keyword.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-person">
<span class="sig-name descname"><span class="pre">--person</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;PERSON&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-export-person" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos with person PERSON. If more than one person, treated as “OR”, e.g. find photos matching any person</p>
@@ -640,6 +645,21 @@ to modify this behavior.</p>
<dd><p>Search for items from a specific year, e.g. year 2022 to find all photos from the year 2022. May be repeated to search multiple years.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-added-before">
<span class="sig-name descname"><span class="pre">--added-before</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;DATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-export-added-before" title="Permalink to this definition">#</a></dt>
<dd><p>Search for items added to the library before a specific date/time, e.g. added-before e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-added-after">
<span class="sig-name descname"><span class="pre">--added-after</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;DATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-export-added-after" title="Permalink to this definition">#</a></dt>
<dd><p>Search for items added to the libray after a specific date/time, e.g. added-after e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-added-in-last">
<span class="sig-name descname"><span class="pre">--added-in-last</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;TIME_DELTA&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-export-added-in-last" title="Permalink to this definition">#</a></dt>
<dd><p>Search for items added to the library in the last TIME_DELTA, where TIME_DELTA is a string like 12 hrs, 1 day, 1d, 1 week, 2weeks, 1 month, 1 year. for example, <cite>added-in-last 7d</cite> and <cite>added-in-last 1 week</cite> are equivalent. months are assumed to be 30 days and years are assumed to be 365 days. Common English abbreviations are accepted, e.g. d, day, days or m, min, minutes.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-has-comment">
<span class="sig-name descname"><span class="pre">--has-comment</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-export-has-comment" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that have comments.</p>
@@ -750,6 +770,11 @@ to modify this behavior.</p>
<dd><p>If used with update, ignores any previously exported files, even if missing from the export folder and only exports new files that havent previously been exported.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-limit">
<span class="sig-name descname"><span class="pre">--limit</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;LIMIT&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-export-limit" title="Permalink to this definition">#</a></dt>
<dd><p>Export at most LIMIT photos. Useful for testing. May be used with update to export incrementally.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-dry-run">
<span class="sig-name descname"><span class="pre">--dry-run</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-export-dry-run" title="Permalink to this definition">#</a></dt>
<dd><p>Dry run (test) the export but dont actually export any files; most useful with verbose.</p>
@@ -984,7 +1009,12 @@ to modify this behavior.</p>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-report">
<span class="sig-name descname"><span class="pre">--report</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;REPORT_FILE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-export-report" title="Permalink to this definition">#</a></dt>
<dd><p>Write a CSV formatted report of all files that were exported.</p>
<dd><p>Write a report of all files that were exported. The extension of the report filename will be used to determine the format. Valid extensions are: .csv (CSV file), .json (JSON), .db and .sqlite (SQLite database). REPORT_FILE may be a template string (see Templating System), for example, report export_{today.date}.csv will write a CSV report file named with todays date. See also append.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-append">
<span class="sig-name descname"><span class="pre">--append</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-export-append" title="Permalink to this definition">#</a></dt>
<dd><p>If used with report, add data to existing report file instead of overwriting it. See also report.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-cleanup">
@@ -1068,6 +1098,95 @@ to modify this behavior.</p>
<dd><p>Required argument</p>
</dd></dl>
</section>
<section id="osxphotos-exportdb">
<h3>exportdb<a class="headerlink" href="#osxphotos-exportdb" title="Permalink to this headline">#</a></h3>
<p>Utilities for working with the osxphotos export database</p>
<div class="highlight-shell notranslate"><div class="highlight"><pre><span></span>osxphotos exportdb <span class="o">[</span>OPTIONS<span class="o">]</span> EXPORT_DATABASE
</pre></div>
</div>
<p class="rubric">Options</p>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-version">
<span class="sig-name descname"><span class="pre">--version</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-version" title="Permalink to this definition">#</a></dt>
<dd><p>Print export database version and exit.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-vacuum">
<span class="sig-name descname"><span class="pre">--vacuum</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-vacuum" title="Permalink to this definition">#</a></dt>
<dd><p>Run VACUUM to defragment the database.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-check-signatures">
<span class="sig-name descname"><span class="pre">--check-signatures</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-check-signatures" title="Permalink to this definition">#</a></dt>
<dd><p>Check signatures for all exported photos in the database to find signatures that dont match.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-update-signatures">
<span class="sig-name descname"><span class="pre">--update-signatures</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-update-signatures" title="Permalink to this definition">#</a></dt>
<dd><p>Update signatures for all exported photos in the database to match on-disk signatures.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-touch-file">
<span class="sig-name descname"><span class="pre">--touch-file</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-touch-file" title="Permalink to this definition">#</a></dt>
<dd><p>Touch files on disk to match created date in Photos library and update export database signatures</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-last-run">
<span class="sig-name descname"><span class="pre">--last-run</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-last-run" title="Permalink to this definition">#</a></dt>
<dd><p>Show last run osxphotos commands used with this database.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-save-config">
<span class="sig-name descname"><span class="pre">--save-config</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;CONFIG_FILE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-save-config" title="Permalink to this definition">#</a></dt>
<dd><p>Save last run configuration to TOML file for use by load-config.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-info">
<span class="sig-name descname"><span class="pre">--info</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;FILE_PATH&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-info" title="Permalink to this definition">#</a></dt>
<dd><p>Print information about FILE_PATH contained in the database.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-report">
<span class="sig-name descname"><span class="pre">--report</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;REPORT_FILE</span> <span class="pre">RUN_ID&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-report" title="Permalink to this definition">#</a></dt>
<dd><p>Generate an export report as <cite>osxphotos export … report REPORT_FILE</cite> would have done. This allows you to re-create an export report if you didnt use the report option when running <cite>osxphotos export</cite>. The extension of the report file is used to determine the format. Valid extensions are: .csv (CSV file), .json (JSON), .db and .sqlite (SQLite database). RUN_ID may be any integer from -10 to 0 specifying which run to use. For example, <cite>report report.csv 0</cite> will generate a CSV report for the last run and <cite>report report.json -1</cite> will generate a JSON report for the second-to-last run (one run prior to last run). REPORT_FILE may be a template string (see Templating System), for example, report export_{today.date}.csv will write a CSV report file named with todays date. See also append.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-migrate">
<span class="sig-name descname"><span class="pre">--migrate</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-migrate" title="Permalink to this definition">#</a></dt>
<dd><p>Migrate (if needed) export database to current version.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-sql">
<span class="sig-name descname"><span class="pre">--sql</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;SQL_STATEMENT&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-sql" title="Permalink to this definition">#</a></dt>
<dd><p>Execute SQL_STATEMENT against export database and print results.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-export-dir">
<span class="sig-name descname"><span class="pre">--export-dir</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;export_dir&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-export-dir" title="Permalink to this definition">#</a></dt>
<dd><p>Optional path to export directory (if not parent of export database).</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-append">
<span class="sig-name descname"><span class="pre">--append</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-append" title="Permalink to this definition">#</a></dt>
<dd><p>If used with report, add data to existing report file instead of overwriting it. See also report.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-V">
<span id="cmdoption-osxphotos-exportdb-v"></span><span id="cmdoption-osxphotos-exportdb-verbose"></span><span class="sig-name descname"><span class="pre">-V</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--verbose</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-V" title="Permalink to this definition">#</a></dt>
<dd><p>Print verbose output.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-dry-run">
<span class="sig-name descname"><span class="pre">--dry-run</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-dry-run" title="Permalink to this definition">#</a></dt>
<dd><p>Run in dry-run mode (dont actually update files), e.g. for use with update-signatures.</p>
</dd></dl>
<p class="rubric">Arguments</p>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-arg-EXPORT_DATABASE">
<span id="cmdoption-osxphotos-exportdb-arg-export-database"></span><span class="sig-name descname"><span class="pre">EXPORT_DATABASE</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-arg-EXPORT_DATABASE" title="Permalink to this definition">#</a></dt>
<dd><p>Required argument</p>
</dd></dl>
</section>
<section id="osxphotos-help">
<h3>help<a class="headerlink" href="#osxphotos-help" title="Permalink to this headline">#</a></h3>
<p>Print help; for help on commands: help &lt;command&gt;.</p>
@@ -1263,6 +1382,11 @@ if more than one option is provided, they are treated as “AND”
<dd><p>Search for photos with keyword KEYWORD. If more than one keyword, treated as “OR”, e.g. find photos matching any keyword</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-no-keyword">
<span class="sig-name descname"><span class="pre">--no-keyword</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-query-no-keyword" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos with no keyword.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-person">
<span class="sig-name descname"><span class="pre">--person</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;PERSON&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-query-person" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos with person PERSON. If more than one person, treated as “OR”, e.g. find photos matching any person</p>
@@ -1518,6 +1642,21 @@ if more than one option is provided, they are treated as “AND”
<dd><p>Search for items from a specific year, e.g. year 2022 to find all photos from the year 2022. May be repeated to search multiple years.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-added-before">
<span class="sig-name descname"><span class="pre">--added-before</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;DATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-query-added-before" title="Permalink to this definition">#</a></dt>
<dd><p>Search for items added to the library before a specific date/time, e.g. added-before e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-added-after">
<span class="sig-name descname"><span class="pre">--added-after</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;DATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-query-added-after" title="Permalink to this definition">#</a></dt>
<dd><p>Search for items added to the libray after a specific date/time, e.g. added-after e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-added-in-last">
<span class="sig-name descname"><span class="pre">--added-in-last</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;TIME_DELTA&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-query-added-in-last" title="Permalink to this definition">#</a></dt>
<dd><p>Search for items added to the library in the last TIME_DELTA, where TIME_DELTA is a string like 12 hrs, 1 day, 1d, 1 week, 2weeks, 1 month, 1 year. for example, <cite>added-in-last 7d</cite> and <cite>added-in-last 1 week</cite> are equivalent. months are assumed to be 30 days and years are assumed to be 365 days. Common English abbreviations are accepted, e.g. d, day, days or m, min, minutes.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-has-comment">
<span class="sig-name descname"><span class="pre">--has-comment</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-query-has-comment" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that have comments.</p>
@@ -1667,6 +1806,11 @@ if more than one option is provided, they are treated as “AND”
<dd><p>Search for photos with keyword KEYWORD. If more than one keyword, treated as “OR”, e.g. find photos matching any keyword</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-no-keyword">
<span class="sig-name descname"><span class="pre">--no-keyword</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-repl-no-keyword" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos with no keyword.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-person">
<span class="sig-name descname"><span class="pre">--person</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;PERSON&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-repl-person" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos with person PERSON. If more than one person, treated as “OR”, e.g. find photos matching any person</p>
@@ -1922,6 +2066,21 @@ if more than one option is provided, they are treated as “AND”
<dd><p>Search for items from a specific year, e.g. year 2022 to find all photos from the year 2022. May be repeated to search multiple years.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-added-before">
<span class="sig-name descname"><span class="pre">--added-before</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;DATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-repl-added-before" title="Permalink to this definition">#</a></dt>
<dd><p>Search for items added to the library before a specific date/time, e.g. added-before e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-added-after">
<span class="sig-name descname"><span class="pre">--added-after</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;DATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-repl-added-after" title="Permalink to this definition">#</a></dt>
<dd><p>Search for items added to the libray after a specific date/time, e.g. added-after e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-added-in-last">
<span class="sig-name descname"><span class="pre">--added-in-last</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;TIME_DELTA&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-repl-added-in-last" title="Permalink to this definition">#</a></dt>
<dd><p>Search for items added to the library in the last TIME_DELTA, where TIME_DELTA is a string like 12 hrs, 1 day, 1d, 1 week, 2weeks, 1 month, 1 year. for example, <cite>added-in-last 7d</cite> and <cite>added-in-last 1 week</cite> are equivalent. months are assumed to be 30 days and years are assumed to be 365 days. Common English abbreviations are accepted, e.g. d, day, days or m, min, minutes.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-has-comment">
<span class="sig-name descname"><span class="pre">--has-comment</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-repl-has-comment" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that have comments.</p>
@@ -2039,16 +2198,28 @@ if more than one option is provided, they are treated as “AND”
</section>
<section id="osxphotos-run">
<h3>run<a class="headerlink" href="#osxphotos-run" title="Permalink to this headline">#</a></h3>
<p>Run a python file using same environment as osxphotos</p>
<div class="highlight-shell notranslate"><div class="highlight"><pre><span></span>osxphotos run <span class="o">[</span>OPTIONS<span class="o">]</span> PYTHON_FILE
<p>Run a python file using same environment as osxphotos.
Any args are made available to the python file.</p>
<div class="highlight-shell notranslate"><div class="highlight"><pre><span></span>osxphotos run <span class="o">[</span>OPTIONS<span class="o">]</span> PYTHON_FILE ARGS
</pre></div>
</div>
<p class="rubric">Options</p>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-run-h">
<span id="cmdoption-osxphotos-run-help"></span><span class="sig-name descname"><span class="pre">-h</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--help</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-run-h" title="Permalink to this definition">#</a></dt>
<dd><p>Show this message and exit</p>
</dd></dl>
<p class="rubric">Arguments</p>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-run-arg-PYTHON_FILE">
<span id="cmdoption-osxphotos-run-arg-python-file"></span><span class="sig-name descname"><span class="pre">PYTHON_FILE</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-run-arg-PYTHON_FILE" title="Permalink to this definition">#</a></dt>
<dd><p>Required argument</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-run-arg-ARGS">
<span id="cmdoption-osxphotos-run-arg-args"></span><span class="sig-name descname"><span class="pre">ARGS</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-run-arg-ARGS" title="Permalink to this definition">#</a></dt>
<dd><p>Optional argument(s)</p>
</dd></dl>
</section>
<section id="osxphotos-snap">
<h3>snap<a class="headerlink" href="#osxphotos-snap" title="Permalink to this headline">#</a></h3>
@@ -2398,6 +2569,7 @@ Commands:
<li><a class="reference internal" href="#osxphotos-docs">docs</a></li>
<li><a class="reference internal" href="#osxphotos-dump">dump</a></li>
<li><a class="reference internal" href="#osxphotos-export">export</a></li>
<li><a class="reference internal" href="#osxphotos-exportdb">exportdb</a></li>
<li><a class="reference internal" href="#osxphotos-help">help</a></li>
<li><a class="reference internal" href="#osxphotos-info">info</a></li>
<li><a class="reference internal" href="#osxphotos-install">install</a></li>

View File

@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#" /><link rel="search" title="Search" href="search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Index - osxphotos 0.48.2 documentation</title>
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Index - osxphotos 0.49.1 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -122,7 +122,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.48.2 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.49.1 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -145,7 +145,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.48.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.1 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -229,6 +229,39 @@
<li><a href="cli.html#cmdoption-osxphotos-query-add-to-album">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-a">osxphotos-timewarp command line option</a>
</li>
</ul></li>
<li>
--added-after
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-added-after">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-added-after">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-added-after">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--added-before
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-added-before">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-added-before">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-added-before">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--added-in-last
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-added-in-last">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-added-in-last">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-added-in-last">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
@@ -247,6 +280,15 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-album-keyword">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
--append
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-append">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-append">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -258,6 +300,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-burst">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-burst">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--check-signatures
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-check-signatures">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -440,6 +489,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-dry-run">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-dry-run">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -545,6 +596,13 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-export-by-date">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
--export-dir
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-export-dir">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -695,6 +753,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-hdr">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-hdr">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--help
<ul>
<li><a href="cli.html#cmdoption-osxphotos-run-h">osxphotos-run command line option</a>
</li>
</ul></li>
<li>
@@ -751,6 +816,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-incloud">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-incloud">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--info
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-info">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -837,6 +909,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-label">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-label">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--last-run
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-last-run">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -844,6 +923,13 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-L">osxphotos-timewarp command line option</a>
</li>
</ul></li>
<li>
--limit
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-limit">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
@@ -898,6 +984,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-max-size">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-max-size">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--migrate
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-migrate">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -953,6 +1046,17 @@
<li><a href="cli.html#cmdoption-osxphotos-query-no-description">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-no-description">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--no-keyword
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-no-keyword">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-no-keyword">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-no-keyword">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
@@ -1017,6 +1121,8 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-not-burst">osxphotos-repl command line option</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
--not-cloudasset
@@ -1048,8 +1154,6 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-not-hdr">osxphotos-repl command line option</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
--not-hidden
@@ -1396,6 +1500,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-report">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-report">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -1417,6 +1523,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-save-config">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-save-config">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -1535,6 +1643,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-slow-mo">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-slow-mo">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--sql
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-sql">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -1646,6 +1761,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-touch-file">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-touch-file">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -1653,6 +1770,13 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-update">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
--update-signatures
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-update-signatures">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -1714,6 +1838,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-uuid-from-file">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-uuid-from-file">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--vacuum
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-vacuum">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -1723,6 +1854,8 @@
<li><a href="cli.html#cmdoption-osxphotos-diff-V">osxphotos-diff command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-V">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-V">osxphotos-exportdb command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-V">osxphotos-timewarp command line option</a>
</li>
@@ -1732,6 +1865,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-v">osxphotos command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-version">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -1808,6 +1943,13 @@
<li><a href="cli.html#cmdoption-osxphotos-timewarp-0">osxphotos-timewarp command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-uuid-f">osxphotos-uuid command line option</a>
</li>
</ul></li>
<li>
-h
<ul>
<li><a href="cli.html#cmdoption-osxphotos-run-h">osxphotos-run command line option</a>
</li>
</ul></li>
<li>
@@ -1900,6 +2042,8 @@
<li><a href="cli.html#cmdoption-osxphotos-diff-V">osxphotos-diff command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-V">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-V">osxphotos-exportdb command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-V">osxphotos-timewarp command line option</a>
</li>
@@ -1934,6 +2078,12 @@
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.SearchInfo.activities">activities (osxphotos.SearchInfo property)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.added_after">added_after (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.added_before">added_before (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.added_in_last">added_in_last (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.ExifTool.addvalues">addvalues() (osxphotos.ExifTool method)</a>
</li>
@@ -1973,6 +2123,13 @@
</li>
<li><a href="reference.html#osxphotos.ExportResults.all_files">all_files() (osxphotos.ExportResults method)</a>
</li>
<li>
ARGS
<ul>
<li><a href="cli.html#cmdoption-osxphotos-run-arg-ARGS">osxphotos-run command line option</a>
</li>
</ul></li>
<li><a href="reference.html#osxphotos.ExifTool.asdict">asdict() (osxphotos.ExifTool method)</a>
<ul>
@@ -2147,10 +2304,10 @@
<li><a href="reference.html#osxphotos.PhotoInfo.exiftool">(osxphotos.PhotoInfo property)</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.ExportOptions.exiftool_flags">exiftool_flags (osxphotos.ExportOptions attribute)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.PhotoExporter.export">export() (osxphotos.PhotoExporter method)</a>
<ul>
@@ -2159,6 +2316,13 @@
</ul></li>
<li><a href="reference.html#osxphotos.ExportOptions.export_as_hardlink">export_as_hardlink (osxphotos.ExportOptions attribute)</a>
</li>
<li>
EXPORT_DATABASE
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-arg-EXPORT_DATABASE">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li><a href="reference.html#osxphotos.ExportOptions.export_db">export_db (osxphotos.ExportOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.ExportDB.export_dir">export_dir (osxphotos.ExportDB property)</a>
@@ -2236,6 +2400,8 @@
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.PhotosDB.get_db_connection">get_db_connection() (osxphotos.PhotosDB method)</a>
</li>
<li><a href="reference.html#osxphotos.ExportDB.get_export_results">get_export_results() (osxphotos.ExportDB method)</a>
</li>
<li><a href="reference.html#osxphotos.ExportDB.get_file_record">get_file_record() (osxphotos.ExportDB method)</a>
</li>
@@ -2246,11 +2412,11 @@
<li><a href="reference.html#osxphotos.PhotoTemplate.get_photo_video_type">get_photo_video_type() (osxphotos.PhotoTemplate method)</a>
</li>
<li><a href="reference.html#osxphotos.ExportDB.get_photoinfo_for_uuid">get_photoinfo_for_uuid() (osxphotos.ExportDB method)</a>
</li>
<li><a href="reference.html#osxphotos.ExportDB.get_previous_uuids">get_previous_uuids() (osxphotos.ExportDB method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.ExportDB.get_previous_uuids">get_previous_uuids() (osxphotos.ExportDB method)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoTemplate.get_template_value">get_template_value() (osxphotos.PhotoTemplate method)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoTemplate.get_template_value_exiftool">get_template_value_exiftool() (osxphotos.PhotoTemplate method)</a>
@@ -2296,6 +2462,8 @@
</li>
</ul></li>
<li><a href="reference.html#osxphotos.PhotoInfo.height">height (osxphotos.PhotoInfo property)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoInfo.hexdigest">hexdigest (osxphotos.PhotoInfo property)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoInfo.hidden">hidden (osxphotos.PhotoInfo property)</a>
@@ -2513,6 +2681,8 @@
<li><a href="reference.html#osxphotos.QueryOptions.no_comment">no_comment (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.no_description">no_description (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.no_keyword">no_keyword (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.no_likes">no_likes (osxphotos.QueryOptions attribute)</a>
</li>
@@ -2527,11 +2697,11 @@
<li><a href="reference.html#osxphotos.QueryOptions.not_cloudasset">not_cloudasset (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_favorite">not_favorite (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_hdr">not_hdr (osxphotos.QueryOptions attribute)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.QueryOptions.not_hdr">not_hdr (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_hidden">not_hidden (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_in_album">not_in_album (osxphotos.QueryOptions attribute)</a>
@@ -2652,10 +2822,18 @@
<li><a href="cli.html#cmdoption-osxphotos-export-add-missing-to-album">--add-missing-to-album</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-add-skipped-to-album">--add-skipped-to-album</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-added-after">--added-after</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-added-before">--added-before</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-added-in-last">--added-in-last</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-album">--album</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-album-keyword">--album-keyword</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-append">--append</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-burst">--burst</a>
</li>
@@ -2754,6 +2932,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-keyword-template">--keyword-template</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-label">--label</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-limit">--limit</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-live">--live</a>
</li>
@@ -2772,6 +2952,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-no-comment">--no-comment</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-no-description">--no-description</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-no-keyword">--no-keyword</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-no-likes">--no-likes</a>
</li>
@@ -2926,6 +3108,45 @@
<li><a href="cli.html#cmdoption-osxphotos-export-arg-DEST">DEST</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-arg-PHOTOS_LIBRARY">PHOTOS_LIBRARY</a>
</li>
</ul></li>
<li>
osxphotos-exportdb command line option
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-append">--append</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-check-signatures">--check-signatures</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-dry-run">--dry-run</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-export-dir">--export-dir</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-info">--info</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-last-run">--last-run</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-migrate">--migrate</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-report">--report</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-save-config">--save-config</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-sql">--sql</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-touch-file">--touch-file</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-update-signatures">--update-signatures</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-vacuum">--vacuum</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-V">--verbose</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-version">--version</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-V">-V</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-arg-EXPORT_DATABASE">EXPORT_DATABASE</a>
</li>
</ul></li>
<li>
@@ -3015,6 +3236,12 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-query-add-to-album">--add-to-album</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-added-after">--added-after</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-added-before">--added-before</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-added-in-last">--added-in-last</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-album">--album</a>
</li>
@@ -3085,6 +3312,8 @@
<li><a href="cli.html#cmdoption-osxphotos-query-no-comment">--no-comment</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-no-description">--no-description</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-no-keyword">--no-keyword</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-no-likes">--no-likes</a>
</li>
@@ -3181,6 +3410,12 @@
osxphotos-repl command line option
<ul>
<li><a href="cli.html#cmdoption-osxphotos-repl-added-after">--added-after</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-added-before">--added-before</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-added-in-last">--added-in-last</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-album">--album</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-burst">--burst</a>
@@ -3250,6 +3485,8 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-no-comment">--no-comment</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-no-description">--no-description</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-no-keyword">--no-keyword</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-no-likes">--no-likes</a>
</li>
@@ -3342,6 +3579,12 @@
osxphotos-run command line option
<ul>
<li><a href="cli.html#cmdoption-osxphotos-run-h">--help</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-run-h">-h</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-run-arg-ARGS">ARGS</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-run-arg-PYTHON_FILE">PYTHON_FILE</a>
</li>
</ul></li>
@@ -3730,6 +3973,8 @@
<li><a href="reference.html#osxphotos.ExportDB.set_config">set_config() (osxphotos.ExportDB method)</a>
</li>
<li><a href="reference.html#osxphotos.set_debug">set_debug() (in module osxphotos)</a>
</li>
<li><a href="reference.html#osxphotos.ExportDB.set_export_results">set_export_results() (osxphotos.ExportDB method)</a>
</li>
<li><a href="reference.html#osxphotos.ExportDB.set_photoinfo_for_uuid">set_photoinfo_for_uuid() (osxphotos.ExportDB method)</a>
</li>

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos" href="overview.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos 0.48.2 documentation</title>
<title>osxphotos 0.49.1 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="#"><div class="brand">osxphotos 0.48.2 documentation</div></a>
<a href="#"><div class="brand">osxphotos 0.49.1 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="#">
<span class="sidebar-brand-text">osxphotos 0.48.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.1 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -238,6 +238,7 @@
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-docs">docs</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-dump">dump</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-export">export</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-exportdb">exportdb</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-help">help</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-info">info</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-install">install</a></li>

Binary file not shown.

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Command Line Interface (CLI)" href="cli.html" /><link rel="prev" title="OSXPhotos" href="overview.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>OSXPhotos Tutorial - osxphotos 0.48.2 documentation</title>
<title>OSXPhotos Tutorial - osxphotos 0.49.1 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.48.2 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.49.1 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.48.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.1 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -314,7 +314,7 @@
<h2>Exporting metadata with exported photos<a class="headerlink" href="#exporting-metadata-with-exported-photos" title="Permalink to this headline">#</a></h2>
<p>Photos tracks a tremendous amount of metadata associated with photos in the library such as keywords, faces and persons, reverse geolocation data, and image classification labels. Photos native export capability does not preserve most of this metadata. osxphotos can, however, access and preserve almost all the metadata associated with photos. Using the free <cite>``exiftool`</cite> &lt;<a class="reference external" href="https://exiftool.org/">https://exiftool.org/</a>&gt;`_ app, osxphotos can write metadata to exported photos. Follow the instructions on the exiftool website to install exiftool then you can use the <code class="docutils literal notranslate"><span class="pre">--exiftool</span></code> option to write metadata to exported photos:</p>
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">/path/to/export</span> <span class="pre">--exiftool</span></code></p>
<p>This will write basic metadata such as keywords, persons, and GPS location to the exported files. osxphotos includes several additional options that can be used in conjunction with <code class="docutils literal notranslate"><span class="pre">--exiftool</span></code> to modify the metadata that is written by <code class="docutils literal notranslate"><span class="pre">exiftool</span></code>. For example, you can use the <code class="docutils literal notranslate"><span class="pre">--keyword-template</span></code> option to specify custom keywords (again, via the osxphotos template system). For example, to use the folder and album a photo is in to create hierarchal keywords in the format used by Lightroom Classic:</p>
<p>This will write basic metadata such as keywords, persons, and GPS location to the exported files. osxphotos includes several additional options that can be used in conjunction with <code class="docutils literal notranslate"><span class="pre">--exiftool</span></code> to modify the metadata that is written by <code class="docutils literal notranslate"><span class="pre">exiftool</span></code>. For example, you can use the <code class="docutils literal notranslate"><span class="pre">--keyword-template</span></code> option to specify custom keywords (again, via the osxphotos template system). For example, to use the folder and album a photo is in to create hierarchical keywords in the format used by Lightroom Classic:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>osxphotos export /path/to/export --exiftool --keyword-template "{folder_album(&gt;)}"
│ │
│ │
@@ -325,7 +325,7 @@
for joining the folders and albums. For example,
if photo is in Folder1/Folder2/Album, (&gt;) produces
"Folder1&gt;Folder2&gt;Album" which some programs, such as
Lightroom Classic, treat as hierarchal keywords
Lightroom Classic, treat as hierarchical keywords
</pre></div>
</div>
<p>The above command will write all the regular metadata that <code class="docutils literal notranslate"><span class="pre">--exiftool</span></code> normally writes to the file upon export but will also add an additional keyword in the exported metadata in the form “Folder1&gt;Folder2&gt;Album”. If you did not include the <code class="docutils literal notranslate"><span class="pre">(&gt;)</span></code> in the template string (e.g. <code class="docutils literal notranslate"><span class="pre">{folder_album}</span></code>), folder_album would render in form “Folder1/Folder2/Album”.</p>
@@ -378,6 +378,8 @@
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">/path/to/export</span> <span class="pre">--portrait</span></code></p>
<p>You can also export photos in a certain date range:</p>
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">/path/to/export</span> <span class="pre">--from-date</span> <span class="pre">"2020-01-01"</span> <span class="pre">--to-date</span> <span class="pre">"2020-02-28"</span></code></p>
<p>or photos added to the library in the last week:</p>
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">/path/to/export</span> <span class="pre">--added-in-last</span> <span class="pre">"1</span> <span class="pre">week"</span></code></p>
</section>
<section id="converting-images-to-jpeg-on-export">
<h2>Converting images to JPEG on export<a class="headerlink" href="#converting-images-to-jpeg-on-export" title="Permalink to this headline">#</a></h2>

View File

@@ -320,7 +320,7 @@ Template Substitutions
* - {crlf}
- a carriage return + line feed: '\r\n'
* - {osxphotos_version}
- The osxphotos version, e.g. '0.48.2'
- The osxphotos version, e.g. '0.49.1'
* - {osxphotos_cmd_line}
- The full command line used to run osxphotos
* - {album}

View File

@@ -195,7 +195,7 @@ Photos tracks a tremendous amount of metadata associated with photos in the libr
``osxphotos export /path/to/export --exiftool``
This will write basic metadata such as keywords, persons, and GPS location to the exported files. osxphotos includes several additional options that can be used in conjunction with ``--exiftool`` to modify the metadata that is written by ``exiftool``. For example, you can use the ``--keyword-template`` option to specify custom keywords (again, via the osxphotos template system). For example, to use the folder and album a photo is in to create hierarchal keywords in the format used by Lightroom Classic:
This will write basic metadata such as keywords, persons, and GPS location to the exported files. osxphotos includes several additional options that can be used in conjunction with ``--exiftool`` to modify the metadata that is written by ``exiftool``. For example, you can use the ``--keyword-template`` option to specify custom keywords (again, via the osxphotos template system). For example, to use the folder and album a photo is in to create hierarchical keywords in the format used by Lightroom Classic:
.. code-block::
@@ -209,7 +209,7 @@ This will write basic metadata such as keywords, persons, and GPS location to th
for joining the folders and albums. For example,
if photo is in Folder1/Folder2/Album, (>) produces
"Folder1>Folder2>Album" which some programs, such as
Lightroom Classic, treat as hierarchal keywords
Lightroom Classic, treat as hierarchical keywords
The above command will write all the regular metadata that ``--exiftool`` normally writes to the file upon export but will also add an additional keyword in the exported metadata in the form "Folder1>Folder2>Album". If you did not include the ``(>)`` in the template string (e.g. ``{folder_album}``\ ), folder_album would render in form "Folder1/Folder2/Album".
@@ -297,6 +297,10 @@ You can also export photos in a certain date range:
``osxphotos export /path/to/export --from-date "2020-01-01" --to-date "2020-02-28"``
or photos added to the library in the last week:
``osxphotos export /path/to/export --added-in-last "1 week"``
Converting images to JPEG on export
-----------------------------------

View File

@@ -0,0 +1,22 @@
"""Use osxphotos and photoscript to find text in photos and update the photo description with detected text"""
import photoscript
import osxphotos
if __name__ == "__main__":
# get photos selected in Photos
selection = photoscript.PhotosLibrary().selection
photosdb = osxphotos.PhotosDB()
photos = photosdb.photos(uuid=[s.uuid for s in selection])
for photo in photos:
detected_text = photo.detected_text()
if not detected_text:
continue
# detected text is tuple of (text, confidence)
for text, confidence in detected_text:
description = photo.description or ""
# set confidence level to whatever you like
if confidence > 0.8 and text not in description:
print(f"Adding {text} to {photo.original_filename} ({photo.uuid})")
photoscript.Photo(photo.uuid).description += f" {text}"

View File

@@ -50,6 +50,7 @@ def post_function(
# exported_album: list of tuples of (filename, album_name) for exported files added to album with --add-exported-to-album
# skipped_album: list of tuples of (filename, album_name) for skipped files added to album with --add-skipped-to-album
# missing_album: list of tuples of (filename, album_name) for missing files added to album with --add-missing-to-album
# metadata_changed: list of filenames that had metadata changes since last export
for filename in results.exported:
# do your processing here

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.48.2"
__version__ = "0.49.1"

View File

@@ -258,6 +258,20 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
ISC_LICENSE = """
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
"""
LICENSE = dedent(
f"""
osxphotos is copyright (c) 2019-2022 by Rhet Turnbull and is licensed under the MIT license:
@@ -273,6 +287,12 @@ osxphotos uses the following 3rd party software licensed under the Apache 2.0 Li
tenacity (Copyright Julien Danjou)
{APACHE_2_0_LICENSE}
osxphotos uses the following 3rd part software licensed under the ISC License:
xdg (Copyright 2016-2021 Scott Stevenson <scott@stevenson.io>)
{ISC_LICENSE}
"""
)

View File

@@ -7,6 +7,7 @@ from datetime import datetime
import click
from packaging import version
from xdg import xdg_config_home, xdg_data_home
import osxphotos
from osxphotos._constants import APP_NAME
@@ -146,6 +147,11 @@ def QUERY_OPTIONS(f):
help="Search for photos with keyword KEYWORD. "
'If more than one keyword, treated as "OR", e.g. find photos matching any keyword',
),
o(
"--no-keyword",
is_flag=True,
help="Search for photos with no keyword.",
),
o(
"--person",
metavar="PERSON",
@@ -380,6 +386,31 @@ def QUERY_OPTIONS(f):
multiple=True,
type=int,
),
o(
"--added-before",
metavar="DATE",
help="Search for items added to the library before a specific date/time, "
"e.g. --added-before e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).",
type=DateTimeISO8601(),
),
o(
"--added-after",
metavar="DATE",
help="Search for items added to the libray after a specific date/time, "
"e.g. --added-after e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).",
type=DateTimeISO8601(),
),
o(
"--added-in-last",
metavar="TIME_DELTA",
help="Search for items added to the library in the last TIME_DELTA, "
"where TIME_DELTA is a string like "
"'12 hrs', '1 day', '1d', '1 week', '2weeks', '1 month', '1 year'. "
"for example, `--added-in-last 7d` and `--added-in-last '1 week'` are equivalent. "
"months are assumed to be 30 days and years are assumed to be 365 days. "
"Common English abbreviations are accepted, e.g. d, day, days or m, min, minutes.",
type=TimeOffset(),
),
o("--has-comment", is_flag=True, help="Search for photos that have comments."),
o("--no-comment", is_flag=True, help="Search for photos with no comments."),
o("--has-likes", is_flag=True, help="Search for photos that have likes."),
@@ -552,12 +583,20 @@ def load_uuid_from_file(filename):
def get_config_dir() -> pathlib.Path:
"""Get the directory where config files are stored; create it if necessary."""
config_dir = pathlib.Path.home() / ".config" / APP_NAME
config_dir = xdg_config_home() / APP_NAME
if not config_dir.is_dir():
config_dir.mkdir(parents=True)
return config_dir
def get_data_dir() -> pathlib.Path:
"""Get the director where local user data files are stored; create it if necessary"""
data_dir = xdg_data_home() / APP_NAME
if not data_dir.is_dir():
data_dir.mkdir(parents=True)
return data_dir
def check_version():
"""Check for updates"""
latest_version, _ = get_latest_version()
@@ -569,4 +608,3 @@ def check_version():
"to suppress this message and prevent osxphotos from checking for latest version.",
err=True,
)

View File

@@ -3,13 +3,14 @@
import pathlib
import shutil
import zipfile
from contextlib import suppress
from typing import Optional
import click
from osxphotos._version import __version__
from .common import get_config_dir
from .common import get_config_dir, get_data_dir
@click.command()
@@ -18,7 +19,14 @@ from .common import get_config_dir
def docs(ctx, cli_obj):
"""Open osxphotos documentation in your browser."""
docs_dir = get_config_dir() / "docs"
# first check if docs installed in old location in confir dir and if so, delete them
old_docs_dir = get_config_dir() / "docs"
if old_docs_dir.exists():
with suppress(Exception):
shutil.rmtree(str(old_docs_dir))
# now check if docs installed in correct location in data dir and if not, copy them
docs_dir = get_data_dir() / "docs"
docs_version = get_docs_version(docs_dir)
if not docs_version or docs_version != __version__:
click.echo(f"Copying docs for osxphotos version {__version__}")
@@ -50,10 +58,10 @@ def copy_docs():
# docs are in osxphotos/docs and this file is in osxphotos/cli
src_dir = pathlib.Path(__file__).parent.parent / "docs"
docs_zip = src_dir / "docs.zip"
config_dir = get_config_dir()
data_dir = get_data_dir()
with zipfile.ZipFile(str(docs_zip), "r") as zf:
zf.extractall(path=str(config_dir))
set_docs_version(config_dir / "docs", __version__)
zf.extractall(path=str(data_dir))
set_docs_version(data_dir / "docs", __version__)
def set_docs_version(docs_dir: pathlib.Path, version: str):

View File

@@ -47,6 +47,7 @@ from osxphotos.export_db import ExportDB, ExportDBInMemory
from osxphotos.fileutil import FileUtil, FileUtilNoOp
from osxphotos.path_utils import is_valid_filepath, sanitize_filename, sanitize_filepath
from osxphotos.photoexporter import ExportOptions, ExportResults, PhotoExporter
from osxphotos.photoinfo import PhotoInfoNone
from osxphotos.photokit import (
check_photokit_authorization,
request_photokit_authorization,
@@ -55,7 +56,7 @@ from osxphotos.photosalbum import PhotosAlbum
from osxphotos.phototemplate import PhotoTemplate, RenderOptions
from osxphotos.queryoptions import QueryOptions
from osxphotos.uti import get_preferred_uti_extension
from osxphotos.utils import format_sec_to_hhmmss, normalize_fs_path
from osxphotos.utils import format_sec_to_hhmmss, normalize_fs_path, pluralize
from .click_rich_echo import (
rich_click_echo,
@@ -85,6 +86,7 @@ from .common import (
from .help import ExportCommand, get_help_msg
from .list import _list_libraries
from .param_types import ExportDBType, FunctionCall, TemplateString
from .report_writer import report_writer_factory, ReportWriterNoOp
from .rich_progress import rich_progress
from .verbose import get_verbose_console, time_stamp, verbose_print
@@ -138,6 +140,13 @@ from .verbose import get_verbose_console, time_stamp, verbose_print
help="If used with --update, ignores any previously exported files, even if missing from "
"the export folder and only exports new files that haven't previously been exported.",
)
@click.option(
"--limit",
metavar="LIMIT",
help="Export at most LIMIT photos. "
"Useful for testing. May be used with --update to export incrementally.",
type=int,
)
@click.option(
"--dry-run",
is_flag=True,
@@ -506,8 +515,20 @@ from .verbose import get_verbose_console, time_stamp, verbose_print
@click.option(
"--report",
metavar="REPORT_FILE",
help="Write a CSV formatted report of all files that were exported.",
type=click.Path(),
help="Write a report of all files that were exported. "
"The extension of the report filename will be used to determine the format. "
"Valid extensions are: "
".csv (CSV file), .json (JSON), .db and .sqlite (SQLite database). "
"REPORT_FILE may be a template string (see Templating System), for example, "
"--report 'export_{today.date}.csv' will write a CSV report file named with today's date. "
"See also --append.",
type=TemplateString(),
)
@click.option(
"--append",
is_flag=True,
help="If used with --report, add data to existing report file instead of overwriting it. "
"See also --report.",
)
@click.option(
"--cleanup",
@@ -672,145 +693,151 @@ def export(
cli_obj,
db,
photos_library,
keyword,
person,
album,
folder,
uuid,
name,
uuid_from_file,
title,
no_title,
description,
no_description,
uti,
ignore_case,
edited,
external_edit,
favorite,
not_favorite,
hidden,
not_hidden,
shared,
not_shared,
from_date,
to_date,
from_time,
to_time,
year,
verbose,
timestamp,
no_progress,
missing,
update,
force_update,
ignore_signature,
only_new,
dry_run,
export_as_hardlink,
touch_file,
overwrite,
retry,
export_by_date,
skip_edited,
skip_original_if_edited,
skip_bursts,
skip_live,
skip_raw,
skip_uuid,
skip_uuid_from_file,
person_keyword,
add_exported_to_album,
add_missing_to_album,
add_skipped_to_album,
added_after,
added_before,
added_in_last,
album_keyword,
keyword_template,
replace_keywords,
description_template,
finder_tag_template,
finder_tag_keywords,
xattr_template,
current_name,
convert_to_jpeg,
jpeg_quality,
sidecar,
sidecar_drop_ext,
only_photos,
only_movies,
album,
append,
beta,
burst,
not_burst,
live,
not_live,
download_missing,
cleanup,
config_only,
convert_to_jpeg,
current_name,
deleted_only,
deleted,
description_template,
description,
dest,
exiftool,
exiftool_path,
exiftool_option,
directory,
download_missing,
dry_run,
duplicate,
edited_suffix,
edited,
exif,
exiftool_merge_keywords,
exiftool_merge_persons,
ignore_date_modified,
portrait,
not_portrait,
screenshot,
not_screenshot,
slow_mo,
not_slow_mo,
time_lapse,
not_time_lapse,
hdr,
not_hdr,
selfie,
not_selfie,
panorama,
not_panorama,
has_raw,
directory,
filename_template,
jpeg_ext,
strip,
edited_suffix,
original_suffix,
place,
no_place,
location,
no_location,
has_comment,
no_comment,
has_likes,
no_likes,
label,
deleted,
deleted_only,
use_photos_export,
use_photokit,
report,
cleanup,
add_exported_to_album,
add_skipped_to_album,
add_missing_to_album,
exiftool_option,
exiftool_path,
exiftool,
export_as_hardlink,
export_by_date,
exportdb,
ramdb,
tmpdir,
load_config,
save_config,
config_only,
is_reference,
beta,
external_edit,
favorite,
filename_template,
finder_tag_keywords,
finder_tag_template,
folder,
force_update,
from_date,
from_time,
has_comment,
has_likes,
has_raw,
hdr,
hidden,
ignore_case,
ignore_date_modified,
ignore_signature,
in_album,
not_in_album,
min_size,
is_reference,
jpeg_ext,
jpeg_quality,
keyword_template,
keyword,
label,
limit,
live,
load_config,
location,
max_size,
regex,
selected,
exif,
query_eval,
query_function,
duplicate,
min_size,
missing,
name,
no_comment,
no_description,
no_likes,
no_location,
no_keyword,
no_place,
no_progress,
no_title,
not_burst,
not_favorite,
not_hdr,
not_hidden,
not_in_album,
not_live,
not_panorama,
not_portrait,
not_screenshot,
not_selfie,
not_shared,
not_slow_mo,
not_time_lapse,
only_movies,
only_new,
only_photos,
original_suffix,
overwrite,
panorama,
person_keyword,
person,
place,
portrait,
post_command,
post_function,
preview,
preview_suffix,
preview_if_missing,
profile,
preview_suffix,
preview,
profile_sort,
profile,
query_eval,
query_function,
ramdb,
regex,
replace_keywords,
report,
retry,
save_config,
screenshot,
selected,
selfie,
shared,
sidecar_drop_ext,
sidecar,
skip_bursts,
skip_edited,
skip_live,
skip_original_if_edited,
skip_raw,
skip_uuid_from_file,
skip_uuid,
slow_mo,
strip,
theme,
time_lapse,
timestamp,
title,
tmpdir,
to_date,
to_time,
touch_file,
update,
use_photokit,
use_photos_export,
uti,
uuid_from_file,
uuid,
verbose,
xattr_template,
year,
debug, # debug, watch, breakpoint handled in cli/__init__.py
watch,
breakpoint,
@@ -886,11 +913,15 @@ def export(
# re-set the local vars to the corresponding config value
# this isn't elegant but avoids having to rewrite this function to use cfg.varname for every parameter
added_after = cfg.added_after
added_before = cfg.added_before
added_in_last = cfg.added_in_last
add_exported_to_album = cfg.add_exported_to_album
add_missing_to_album = cfg.add_missing_to_album
add_skipped_to_album = cfg.add_skipped_to_album
album = cfg.album
album_keyword = cfg.album_keyword
append = cfg.append
beta = cfg.beta
burst = cfg.burst
cleanup = cfg.cleanup
@@ -939,6 +970,7 @@ def export(
keyword = cfg.keyword
keyword_template = cfg.keyword_template
label = cfg.label
limit = cfg.limit
live = cfg.live
location = cfg.location
max_size = cfg.max_size
@@ -949,6 +981,7 @@ def export(
no_description = cfg.no_description
no_likes = cfg.no_likes
no_location = cfg.no_location
no_keyword = cfg.no_keyword
no_place = cfg.no_place
no_progress = cfg.no_progress
no_title = cfg.no_title
@@ -1038,41 +1071,43 @@ def export(
# validate options
exclusive_options = [
("favorite", "not_favorite"),
("hidden", "not_hidden"),
("title", "no_title"),
("description", "no_description"),
("only_photos", "only_movies"),
("burst", "not_burst"),
("live", "not_live"),
("portrait", "not_portrait"),
("screenshot", "not_screenshot"),
("slow_mo", "not_slow_mo"),
("time_lapse", "not_time_lapse"),
("hdr", "not_hdr"),
("selfie", "not_selfie"),
("panorama", "not_panorama"),
("export_by_date", "directory"),
("export_as_hardlink", "exiftool"),
("place", "no_place"),
("deleted", "deleted_only"),
("skip_edited", "skip_original_if_edited"),
("description", "no_description"),
("export_as_hardlink", "convert_to_jpeg"),
("export_as_hardlink", "download_missing"),
("shared", "not_shared"),
("export_as_hardlink", "exiftool"),
("export_by_date", "directory"),
("favorite", "not_favorite"),
("has_comment", "no_comment"),
("has_likes", "no_likes"),
("hdr", "not_hdr"),
("hidden", "not_hidden"),
("in_album", "not_in_album"),
("live", "not_live"),
("location", "no_location"),
("keyword", "no_keyword"),
("only_photos", "only_movies"),
("panorama", "not_panorama"),
("place", "no_place"),
("portrait", "not_portrait"),
("screenshot", "not_screenshot"),
("selfie", "not_selfie"),
("shared", "not_shared"),
("skip_edited", "skip_original_if_edited"),
("slow_mo", "not_slow_mo"),
("time_lapse", "not_time_lapse"),
("title", "no_title"),
]
dependent_options = [
("missing", ("download_missing", "use_photos_export")),
("jpeg_quality", ("convert_to_jpeg")),
("ignore_signature", ("update", "force_update")),
("only_new", ("update", "force_update")),
("exiftool_option", ("exiftool")),
("exiftool_merge_keywords", ("exiftool", "sidecar")),
("exiftool_merge_persons", ("exiftool", "sidecar")),
("exiftool_option", ("exiftool")),
("ignore_signature", ("update", "force_update")),
("jpeg_quality", ("convert_to_jpeg")),
("missing", ("download_missing", "use_photos_export")),
("only_new", ("update", "force_update")),
("append", ("report")),
]
try:
cfg.validate(exclusive=exclusive_options, dependent=dependent_options, cli=True)
@@ -1131,12 +1166,11 @@ def export(
dest = str(pathlib.Path(dest).resolve())
if report and os.path.isdir(report):
rich_click_echo(
f"[error]report is a directory, must be file name",
err=True,
)
sys.exit(1)
if report:
report = render_and_validate_report(report, exiftool_path, dest)
report_writer = report_writer_factory(report, append)
else:
report_writer = ReportWriterNoOp()
# if use_photokit and not check_photokit_authorization():
# click.echo(
@@ -1267,83 +1301,87 @@ def export(
photosdb._beta = beta
query_options = QueryOptions(
keyword=keyword,
person=person,
added_after=added_after,
added_before=added_before,
added_in_last=added_in_last,
album=album,
folder=folder,
uuid=uuid,
title=title,
no_title=no_title,
burst_photos=export_bursts,
burst=burst,
cloudasset=False,
deleted_only=deleted_only,
deleted=deleted,
description=description,
no_description=no_description,
ignore_case=ignore_case,
duplicate=duplicate,
edited=edited,
exif=exif,
external_edit=external_edit,
favorite=favorite,
not_favorite=not_favorite,
hidden=hidden,
not_hidden=not_hidden,
missing=missing,
not_missing=None,
shared=shared,
not_shared=not_shared,
photos=photos,
movies=movies,
uti=uti,
burst=burst,
not_burst=not_burst,
live=live,
not_live=not_live,
cloudasset=False,
not_cloudasset=False,
incloud=False,
not_incloud=False,
folder=folder,
from_date=from_date,
to_date=to_date,
from_time=from_time,
to_time=to_time,
year=year,
portrait=portrait,
not_portrait=not_portrait,
screenshot=screenshot,
not_screenshot=not_screenshot,
slow_mo=slow_mo,
not_slow_mo=not_slow_mo,
time_lapse=time_lapse,
not_time_lapse=not_time_lapse,
hdr=hdr,
not_hdr=not_hdr,
selfie=selfie,
not_selfie=not_selfie,
panorama=panorama,
not_panorama=not_panorama,
has_raw=has_raw,
place=place,
no_place=no_place,
location=location,
no_location=no_location,
label=label,
deleted=deleted,
deleted_only=deleted_only,
function=query_function,
has_comment=has_comment,
no_comment=no_comment,
has_likes=has_likes,
no_likes=no_likes,
is_reference=is_reference,
has_raw=has_raw,
hdr=hdr,
hidden=hidden,
ignore_case=ignore_case,
in_album=in_album,
not_in_album=not_in_album,
burst_photos=export_bursts,
incloud=False,
is_reference=is_reference,
keyword=keyword,
label=label,
live=live,
location=location,
max_size=max_size,
min_size=min_size,
# skip missing bursts if using --download-missing by itself as AppleScript otherwise causes errors
missing_bursts=(download_missing and use_photokit) or not download_missing,
missing=missing,
movies=movies,
name=name,
min_size=min_size,
max_size=max_size,
regex=regex,
selected=selected,
exif=exif,
no_comment=no_comment,
no_description=no_description,
no_likes=no_likes,
no_location=no_location,
no_keyword=no_keyword,
no_place=no_place,
no_title=no_title,
not_burst=not_burst,
not_cloudasset=False,
not_favorite=not_favorite,
not_hdr=not_hdr,
not_hidden=not_hidden,
not_in_album=not_in_album,
not_incloud=False,
not_live=not_live,
not_missing=None,
not_panorama=not_panorama,
not_portrait=not_portrait,
not_screenshot=not_screenshot,
not_selfie=not_selfie,
not_shared=not_shared,
not_slow_mo=not_slow_mo,
not_time_lapse=not_time_lapse,
panorama=panorama,
person=person,
photos=photos,
place=place,
portrait=portrait,
query_eval=query_eval,
function=query_function,
duplicate=duplicate,
regex=regex,
screenshot=screenshot,
selected=selected,
selfie=selfie,
shared=shared,
slow_mo=slow_mo,
time_lapse=time_lapse,
title=title,
to_date=to_date,
to_time=to_time,
uti=uti,
uuid=uuid,
year=year,
)
try:
@@ -1374,8 +1412,7 @@ def export(
if photos:
num_photos = len(photos)
# TODO: photos or photo appears several times, pull into a separate function
photo_str = "photos" if num_photos > 1 else "photo"
photo_str = pluralize(num_photos, "photo", "photos")
rich_echo(
f"Exporting [num]{num_photos}[/num] {photo_str} to [filepath]{dest}[/]..."
)
@@ -1403,9 +1440,11 @@ def export(
)
photo_num = 0
num_exported = 0
limit_str = f" (limit = [num]{limit}[/num])" if limit else ""
with rich_progress(console=get_verbose_console(), mock=no_progress) as progress:
task = progress.add_task(
f"Exporting [num]{num_photos}[/] photos", total=num_photos
f"Exporting [num]{num_photos}[/] photos{limit_str}", total=num_photos
)
for p in photos:
photo_num += 1
@@ -1553,6 +1592,8 @@ def export(
export_dir=dest,
verbose_=verbose_,
)
export_results.xattr_written.extend(tags_written)
export_results.xattr_skipped.extend(tags_skipped)
results.xattr_written.extend(tags_written)
results.xattr_skipped.extend(tags_skipped)
@@ -1565,12 +1606,29 @@ def export(
export_dir=dest,
verbose_=verbose_,
)
export_results.xattr_written.extend(xattr_written)
export_results.xattr_skipped.extend(xattr_skipped)
results.xattr_written.extend(xattr_written)
results.xattr_skipped.extend(xattr_skipped)
report_writer.write(export_results)
progress.advance(task)
photo_str_total = "photos" if len(photos) != 1 else "photo"
# handle limit
if export_results.exported:
# if any photos were exported, increment num_exported used by limit
# limit considers each PhotoInfo object as a single photo even if multiple files are exported
num_exported += 1
if limit and num_exported >= limit:
# advance progress to end
progress.advance(task, num_photos - photo_num)
break
# store results so they can be used by `osxphotos exportdb --report`
export_db.set_export_results(results)
photo_str_total = pluralize(len(photos), "photo", "photos")
if update or force_update:
summary = (
f"Processed: [num]{len(photos)}[/] {photo_str_total}, "
@@ -1588,6 +1646,8 @@ def export(
summary += f"error: [num]{len(results.error)}[/]"
if touch_file:
summary += f", touched date: [num]{len(results.touched)}[/]"
if limit:
summary += f", limit: [num]{num_exported}[/]/[num]{limit}[/] exported"
rich_echo(summary)
stop_time = time.perf_counter()
rich_echo(f"Elapsed time: [time]{format_sec_to_hhmmss(stop_time-start_time)}")
@@ -1627,12 +1687,15 @@ def export(
rich_echo(
f"Deleted: [num]{len(cleaned_files)}[/num] {file_str}, [num]{len(cleaned_dirs)}[/num] {dir_str}"
)
report_writer.write(
ExportResults(deleted_files=cleaned_files, deleted_directories=cleaned_dirs)
)
results.deleted_files = cleaned_files
results.deleted_directories = cleaned_dirs
if report:
verbose_(f"Writing export report to [filepath]{report}")
write_export_report(report, results)
verbose_(f"Wrote export report to [filepath]{report}")
report_writer.close()
# close export_db and write changes if needed
if ramdb and not dry_run:
@@ -1641,18 +1704,6 @@ def export(
export_db.close()
def _export_with_profiler(args: Dict):
""" "Run export with cProfile"""
try:
args.pop("profile")
except KeyError:
pass
cProfile.runctx(
"_export(**args)", globals=globals(), locals=locals(), sort="tottime"
)
def export_photo(
photo=None,
dest=None,
@@ -2402,150 +2453,6 @@ def find_files_in_branch(pathname, filename):
return files
def write_export_report(report_file, results):
"""write CSV report with results from export
Args:
report_file: path to report file
results: ExportResults object
"""
# Collect results for reporting
all_results = {
result: {
"filename": result,
"exported": 0,
"new": 0,
"updated": 0,
"skipped": 0,
"exif_updated": 0,
"touched": 0,
"converted_to_jpeg": 0,
"sidecar_xmp": 0,
"sidecar_json": 0,
"sidecar_exiftool": 0,
"missing": 0,
"error": "",
"exiftool_warning": "",
"exiftool_error": "",
"extended_attributes_written": 0,
"extended_attributes_skipped": 0,
"cleanup_deleted_file": 0,
"cleanup_deleted_directory": 0,
"exported_album": "",
}
for result in results.all_files()
+ results.deleted_files
+ results.deleted_directories
}
for result in results.exported:
all_results[result]["exported"] = 1
for result in results.new:
all_results[result]["new"] = 1
for result in results.updated:
all_results[result]["updated"] = 1
for result in results.skipped:
all_results[result]["skipped"] = 1
for result in results.exif_updated:
all_results[result]["exif_updated"] = 1
for result in results.touched:
all_results[result]["touched"] = 1
for result in results.converted_to_jpeg:
all_results[result]["converted_to_jpeg"] = 1
for result in results.sidecar_xmp_written:
all_results[result]["sidecar_xmp"] = 1
all_results[result]["exported"] = 1
for result in results.sidecar_xmp_skipped:
all_results[result]["sidecar_xmp"] = 1
all_results[result]["skipped"] = 1
for result in results.sidecar_json_written:
all_results[result]["sidecar_json"] = 1
all_results[result]["exported"] = 1
for result in results.sidecar_json_skipped:
all_results[result]["sidecar_json"] = 1
all_results[result]["skipped"] = 1
for result in results.sidecar_exiftool_written:
all_results[result]["sidecar_exiftool"] = 1
all_results[result]["exported"] = 1
for result in results.sidecar_exiftool_skipped:
all_results[result]["sidecar_exiftool"] = 1
all_results[result]["skipped"] = 1
for result in results.missing:
all_results[result]["missing"] = 1
for result in results.error:
all_results[result[0]]["error"] = result[1]
for result in results.exiftool_warning:
all_results[result[0]]["exiftool_warning"] = result[1]
for result in results.exiftool_error:
all_results[result[0]]["exiftool_error"] = result[1]
for result in results.xattr_written:
all_results[result]["extended_attributes_written"] = 1
for result in results.xattr_skipped:
all_results[result]["extended_attributes_skipped"] = 1
for result in results.deleted_files:
all_results[result]["cleanup_deleted_file"] = 1
for result in results.deleted_directories:
all_results[result]["cleanup_deleted_directory"] = 1
for result, album in results.exported_album:
all_results[result]["exported_album"] = album
report_columns = [
"filename",
"exported",
"new",
"updated",
"skipped",
"exif_updated",
"touched",
"converted_to_jpeg",
"sidecar_xmp",
"sidecar_json",
"sidecar_exiftool",
"missing",
"error",
"exiftool_warning",
"exiftool_error",
"extended_attributes_written",
"extended_attributes_skipped",
"cleanup_deleted_file",
"cleanup_deleted_directory",
"exported_album",
]
try:
with open(report_file, "w") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=report_columns)
writer.writeheader()
for data in [result for result in all_results.values()]:
writer.writerow(data)
except IOError:
rich_echo_error("[error]Could not open output file for writing"),
sys.exit(1)
def cleanup_files(dest_path, files_to_keep, fileutil, verbose_):
"""cleanup dest_path by deleting and files and empty directories
not in files_to_keep
@@ -2791,3 +2698,45 @@ def run_post_command(
rich_echo_error(
f'[error]Error running command "{command}": {run_error}'
)
def render_and_validate_report(report: str, exiftool_path: str, export_dir: str) -> str:
"""Render a report file template and validate the filename
Args:
report: the template string
exiftool_path: the path to the exiftool binary
export_dir: the export directory
Returns:
the rendered report filename
Note:
Exits with error if the report filename is invalid
"""
# render report template and validate the filename
template = PhotoTemplate(PhotoInfoNone(), exiftool_path=exiftool_path)
render_options = RenderOptions(export_dir=export_dir)
report_file, _ = template.render(report, options=render_options)
report = report_file[0]
if os.path.isdir(report):
rich_click_echo(
f"[error]Report '{report}' is a directory, must be file name",
err=True,
)
sys.exit(1)
return report
# def _export_with_profiler(args: Dict):
# """ "Run export with cProfile"""
# try:
# args.pop("profile")
# except KeyError:
# pass
# cProfile.runctx(
# "_export(**args)", globals=globals(), locals=locals(), sort="tottime"
# )

View File

@@ -8,7 +8,11 @@ from rich import print
from osxphotos._constants import OSXPHOTOS_EXPORT_DB
from osxphotos._version import __version__
from osxphotos.export_db import OSXPHOTOS_EXPORTDB_VERSION, ExportDB
from osxphotos.export_db import (
MAX_EXPORT_RESULTS_DATA_ROWS,
OSXPHOTOS_EXPORTDB_VERSION,
ExportDB,
)
from osxphotos.export_db_utils import (
export_db_check_signatures,
export_db_get_last_run,
@@ -19,11 +23,13 @@ from osxphotos.export_db_utils import (
export_db_vacuum,
)
from .common import OSXPHOTOS_HIDDEN
from .export import render_and_validate_report
from .param_types import TemplateString
from .report_writer import report_writer_factory
from .verbose import verbose_print
@click.command(name="exportdb", hidden=OSXPHOTOS_HIDDEN)
@click.command(name="exportdb")
@click.option("--version", is_flag=True, help="Print export database version and exit.")
@click.option("--vacuum", is_flag=True, help="Run VACUUM to defragment the database.")
@click.option(
@@ -57,6 +63,24 @@ from .verbose import verbose_print
nargs=1,
help="Print information about FILE_PATH contained in the database.",
)
@click.option(
"--report",
metavar="REPORT_FILE RUN_ID",
help="Generate an export report as `osxphotos export ... --report REPORT_FILE` would have done. "
"This allows you to re-create an export report if you didn't use the --report option "
"when running `osxphotos export`. "
"The extension of the report file is used to determine the format. "
"Valid extensions are: "
".csv (CSV file), .json (JSON), .db and .sqlite (SQLite database). "
f"RUN_ID may be any integer from {-MAX_EXPORT_RESULTS_DATA_ROWS} to 0 specifying which run to use. "
"For example, `--report report.csv 0` will generate a CSV report for the last run and "
"`--report report.json -1` will generate a JSON report for the second-to-last run "
"(one run prior to last run). "
"REPORT_FILE may be a template string (see Templating System), for example, "
"--report 'export_{today.date}.csv' will write a CSV report file named with today's date. "
"See also --append.",
type=(TemplateString(), click.IntRange(-(MAX_EXPORT_RESULTS_DATA_ROWS - 1), 0)),
)
@click.option(
"--migrate",
is_flag=True,
@@ -72,6 +96,12 @@ from .verbose import verbose_print
help="Optional path to export directory (if not parent of export database).",
type=click.Path(exists=True, file_okay=False, dir_okay=True),
)
@click.option(
"--append",
is_flag=True,
help="If used with --report, add data to existing report file instead of overwriting it. "
"See also --report.",
)
@click.option("--verbose", "-V", is_flag=True, help="Print verbose output.")
@click.option(
"--dry-run",
@@ -88,9 +118,11 @@ def exportdb(
last_run,
save_config,
info,
report,
migrate,
sql,
export_dir,
append,
verbose,
dry_run,
export_db,
@@ -99,6 +131,14 @@ def exportdb(
verbose_ = verbose_print(verbose, rich=True)
# validate options and args
if append and not report:
print(
"[red]Error: --append requires --report; ee --help for more information.[/]",
file=sys.stderr,
)
sys.exit(1)
export_db = pathlib.Path(export_db)
if export_db.is_dir():
# assume it's the export folder
@@ -112,20 +152,27 @@ def exportdb(
export_dir = export_dir or export_db.parent
sub_commands = [
version,
check_signatures,
update_signatures,
touch_file,
last_run,
bool(save_config),
bool(info),
migrate,
bool(sql),
bool(cmd)
for cmd in [
check_signatures,
info,
last_run,
migrate,
report,
save_config,
sql,
touch_file,
update_signatures,
vacuum,
version,
]
]
if sum(sub_commands) > 1:
print("[red]Only a single sub-command may be specified at a time[/red]")
sys.exit(1)
# process sub-commands
# TODO: each of these should be a function call
if version:
try:
osxphotos_ver, export_db_ver = export_db_get_version(export_db)
@@ -221,11 +268,29 @@ def exportdb(
sys.exit(1)
else:
if info_rec:
print(info_rec.asdict())
print(info_rec.json(indent=2))
else:
print(f"[red]File '{info}' not found in export database[/red]")
sys.exit(0)
if report:
exportdb = ExportDB(export_db, export_dir)
report_template, run_id = report
report_filename = render_and_validate_report(report_template, "", export_dir)
export_results = exportdb.get_export_results(run_id)
if not export_results:
print(f"[red]No report results found for run ID {run_id}[/red]")
sys.exit(1)
try:
report_writer = report_writer_factory(report_filename, append=append)
except ValueError as e:
print(f"[red]Error: {e}[/red]")
sys.exit(1)
report_writer.write(export_results)
report_writer.close()
print(f"Wrote report to {report_filename}")
sys.exit(0)
if migrate:
exportdb = ExportDB(export_db, export_dir)
if upgraded := exportdb.was_upgraded:

View File

@@ -6,6 +6,25 @@ from runpy import run_module, run_path
import click
class RunCommand(click.Command):
"""Custom command that ignores unknown options so options can be passed to the run script"""
def make_parser(self, ctx):
"""Creates the underlying option parser for this command."""
parser = click.OptionParser(ctx)
parser.ignore_unknown_options = True
for param in self.get_params(ctx):
param.add_to_parser(parser, ctx)
return parser
def get_usage(self, ctx):
"""Returns the help for this command;
normally it would just return the usage string
but in order to pass --help on to the run script,
help for the run command is handled here"""
return self.get_help(ctx)
@click.command()
@click.argument("packages", nargs=-1, required=True)
@click.option(
@@ -30,8 +49,15 @@ def uninstall(packages, yes):
run_module("pip", run_name="__main__")
@click.command(name="run")
@click.command(name="run", cls=RunCommand)
# help command passed just to keep click from intercepting help
# and allowing --help to be passed to the script being run
@click.option("--help", "-h", is_flag=True, help="Show this message and exit")
@click.argument("python_file", nargs=1, type=click.Path(exists=True))
def run(python_file):
"""Run a python file using same environment as osxphotos"""
@click.argument("args", metavar="ARGS", nargs=-1)
def run(python_file, help, args):
"""Run a python file using same environment as osxphotos.
Any args are made available to the python file."""
# drop first two arguments, which are the osxphotos script and run command
sys.argv = sys.argv[2:]
run_path(python_file, run_name="__main__")

View File

@@ -2,10 +2,11 @@
import datetime
import os
import pathlib
import re
import bitmath
import click
import pytimeparse
import pytimeparse2
from osxphotos.export_db_utils import export_db_get_version
from osxphotos.photoinfo import PhotoInfoNone
@@ -159,19 +160,31 @@ class DateOffset(click.ParamType):
name = "DATEOFFSET"
def convert(self, value, param, ctx):
offset = pytimeparse.parse(value)
# if it's a single number treat it as days
# but pytimeparse2 treats is as seconds so need to verify it's just a number and if so,
# convert it to seconds
value = value.strip()
if re.match(r"^[+-]?\s*?\d+$", value):
# just a number
# strip any whitespace, e.g. for "+ 1" or "- 1"
value = "".join(value.split())
try:
return datetime.timedelta(days=int(value))
except ValueError:
self.fail(
f"Invalid date offset format: {value}. "
"Valid format for date/time offset: '±D days', '±W weeks', '±M months', '±D' where D is days "
)
offset = pytimeparse2.parse(value)
if offset is not None:
offset = offset / 86400
return datetime.timedelta(days=offset)
# could be in format "-1" (negative offset) or "+1" (positive offset)
try:
return datetime.timedelta(days=int(value))
except ValueError:
self.fail(
f"Invalid date offset format: {value}. "
"Valid format for date/time offset: '±D days', '±W weeks', '±D' where D is days "
)
self.fail(
f"Invalid date offset format: {value}. "
"Valid format for date/time offset: '±D days', '±W weeks', '±M months', '±D' where D is days "
)
class TimeOffset(click.ParamType):
@@ -180,18 +193,14 @@ class TimeOffset(click.ParamType):
name = "TIMEOFFSET"
def convert(self, value, param, ctx):
offset = pytimeparse.parse(value)
offset = pytimeparse2.parse(value)
if offset is not None:
return datetime.timedelta(seconds=offset)
# could be in format "-18000" (negative offset) or "+18000" (positive offset)
try:
return datetime.timedelta(seconds=int(value))
except ValueError:
self.fail(
f"Invalid time offset format: {value}. "
"Valid format for date/time offset: '±HH:MM:SS', '±H hours' (or hr), '±M minutes' (or min), '±S seconds' (or sec), '±S' (where S is seconds)"
)
self.fail(
f"Invalid time offset format: {value}. "
"Valid format for date/time offset: '±HH:MM:SS', '±H hours' (or hr), '±M minutes' (or min), '±S seconds' (or sec), '±S' (where S is seconds)"
)
class UTCOffset(click.ParamType):

View File

@@ -73,83 +73,87 @@ def query(
cli_obj,
db,
photos_library,
keyword,
person,
add_to_album,
added_after,
added_before,
added_in_last,
album,
folder,
name,
uuid,
uuid_from_file,
title,
no_title,
burst,
cloudasset,
deleted_only,
deleted,
description,
no_description,
ignore_case,
json_,
duplicate,
edited,
exif,
external_edit,
favorite,
not_favorite,
folder,
from_date,
from_time,
has_comment,
has_likes,
has_raw,
hdr,
hidden,
not_hidden,
ignore_case,
in_album,
incloud,
is_reference,
json_,
keyword,
label,
live,
location,
max_size,
min_size,
missing,
name,
no_comment,
no_description,
no_likes,
no_location,
no_keyword,
no_place,
no_title,
not_burst,
not_cloudasset,
not_favorite,
not_hdr,
not_hidden,
not_in_album,
not_incloud,
not_live,
not_missing,
shared,
not_panorama,
not_portrait,
not_screenshot,
not_selfie,
not_shared,
not_slow_mo,
not_time_lapse,
only_movies,
only_photos,
uti,
burst,
not_burst,
live,
not_live,
cloudasset,
not_cloudasset,
incloud,
not_incloud,
from_date,
to_date,
from_time,
to_time,
year,
portrait,
not_portrait,
screenshot,
not_screenshot,
slow_mo,
not_slow_mo,
time_lapse,
not_time_lapse,
hdr,
not_hdr,
selfie,
not_selfie,
panorama,
not_panorama,
has_raw,
person,
place,
no_place,
location,
no_location,
label,
deleted,
deleted_only,
has_comment,
no_comment,
has_likes,
no_likes,
is_reference,
in_album,
not_in_album,
duplicate,
min_size,
max_size,
regex,
selected,
exif,
portrait,
query_eval,
query_function,
add_to_album,
regex,
screenshot,
selected,
selfie,
shared,
slow_mo,
time_lapse,
title,
to_date,
to_time,
uti,
uuid_from_file,
uuid,
year,
debug, # handled in cli/__init__.py
):
"""Query the Photos database using 1 or more search options;
@@ -160,58 +164,62 @@ def query(
# if no query terms, show help and return
# sanity check input args
nonexclusive = [
keyword,
person,
added_after,
added_before,
added_in_last,
album,
folder,
name,
uuid,
uuid_from_file,
duplicate,
edited,
exif,
external_edit,
uti,
has_raw,
folder,
from_date,
to_date,
from_time,
to_time,
year,
label,
has_raw,
is_reference,
keyword,
label,
max_size,
min_size,
name,
person,
query_eval,
query_function,
min_size,
max_size,
regex,
selected,
exif,
duplicate,
to_date,
to_time,
uti,
uuid_from_file,
uuid,
year,
]
exclusive = [
(favorite, not_favorite),
(hidden, not_hidden),
(missing, not_missing),
(any(title), no_title),
(any(description), no_description),
(only_photos, only_movies),
(burst, not_burst),
(live, not_live),
(cloudasset, not_cloudasset),
(incloud, not_incloud),
(portrait, not_portrait),
(screenshot, not_screenshot),
(slow_mo, not_slow_mo),
(time_lapse, not_time_lapse),
(hdr, not_hdr),
(selfie, not_selfie),
(panorama, not_panorama),
(any(place), no_place),
(any(title), no_title),
(any(keyword), no_keyword),
(burst, not_burst),
(cloudasset, not_cloudasset),
(deleted, deleted_only),
(shared, not_shared),
(favorite, not_favorite),
(has_comment, no_comment),
(has_likes, no_likes),
(hdr, not_hdr),
(hidden, not_hidden),
(in_album, not_in_album),
(incloud, not_incloud),
(live, not_live),
(location, no_location),
(missing, not_missing),
(only_photos, only_movies),
(panorama, not_panorama),
(portrait, not_portrait),
(screenshot, not_screenshot),
(selfie, not_selfie),
(shared, not_shared),
(slow_mo, not_slow_mo),
(time_lapse, not_time_lapse),
]
# print help if no non-exclusive term or a double exclusive term is given
if any(all(bb) for bb in exclusive) or not any(
@@ -247,80 +255,84 @@ def query(
photosdb = osxphotos.PhotosDB(dbfile=db)
query_options = QueryOptions(
keyword=keyword,
person=person,
added_after=added_after,
added_before=added_before,
added_in_last=added_in_last,
album=album,
folder=folder,
uuid=uuid,
title=title,
no_title=no_title,
burst=burst,
cloudasset=cloudasset,
deleted_only=deleted_only,
deleted=deleted,
description=description,
no_description=no_description,
ignore_case=ignore_case,
duplicate=duplicate,
edited=edited,
exif=exif,
external_edit=external_edit,
favorite=favorite,
not_favorite=not_favorite,
hidden=hidden,
not_hidden=not_hidden,
missing=missing,
not_missing=not_missing,
shared=shared,
not_shared=not_shared,
photos=photos,
movies=movies,
uti=uti,
burst=burst,
not_burst=not_burst,
live=live,
not_live=not_live,
cloudasset=cloudasset,
not_cloudasset=not_cloudasset,
incloud=incloud,
not_incloud=not_incloud,
folder=folder,
from_date=from_date,
to_date=to_date,
from_time=from_time,
to_time=to_time,
year=year,
portrait=portrait,
not_portrait=not_portrait,
screenshot=screenshot,
not_screenshot=not_screenshot,
slow_mo=slow_mo,
not_slow_mo=not_slow_mo,
time_lapse=time_lapse,
not_time_lapse=not_time_lapse,
hdr=hdr,
not_hdr=not_hdr,
selfie=selfie,
not_selfie=not_selfie,
panorama=panorama,
not_panorama=not_panorama,
has_raw=has_raw,
place=place,
no_place=no_place,
location=location,
no_location=no_location,
label=label,
deleted=deleted,
deleted_only=deleted_only,
has_comment=has_comment,
no_comment=no_comment,
has_likes=has_likes,
no_likes=no_likes,
is_reference=is_reference,
in_album=in_album,
not_in_album=not_in_album,
name=name,
min_size=min_size,
max_size=max_size,
query_eval=query_eval,
function=query_function,
has_comment=has_comment,
has_likes=has_likes,
has_raw=has_raw,
hdr=hdr,
hidden=hidden,
ignore_case=ignore_case,
in_album=in_album,
incloud=incloud,
is_reference=is_reference,
keyword=keyword,
label=label,
live=live,
location=location,
max_size=max_size,
min_size=min_size,
missing=missing,
movies=movies,
name=name,
no_comment=no_comment,
no_description=no_description,
no_likes=no_likes,
no_location=no_location,
no_keyword=no_keyword,
no_place=no_place,
no_title=no_title,
not_burst=not_burst,
not_cloudasset=not_cloudasset,
not_favorite=not_favorite,
not_hdr=not_hdr,
not_hidden=not_hidden,
not_in_album=not_in_album,
not_incloud=not_incloud,
not_live=not_live,
not_missing=not_missing,
not_panorama=not_panorama,
not_portrait=not_portrait,
not_screenshot=not_screenshot,
not_selfie=not_selfie,
not_shared=not_shared,
not_slow_mo=not_slow_mo,
not_time_lapse=not_time_lapse,
panorama=panorama,
person=person,
photos=photos,
place=place,
portrait=portrait,
query_eval=query_eval,
regex=regex,
screenshot=screenshot,
selected=selected,
exif=exif,
duplicate=duplicate,
selfie=selfie,
shared=shared,
slow_mo=slow_mo,
time_lapse=time_lapse,
title=title,
to_date=to_date,
to_time=to_time,
uti=uti,
uuid=uuid,
year=year,
)
try:

View File

@@ -241,55 +241,59 @@ def _query_options_from_kwargs(**kwargs) -> QueryOptions:
"""Validate query options and create a QueryOptions instance"""
# sanity check input args
nonexclusive = [
"keyword",
"person",
"added_after",
"added_before",
"added_in_last",
"album",
"folder",
"name",
"uuid",
"uuid_from_file",
"duplicate",
"edited",
"exif",
"external_edit",
"uti",
"has_raw",
"folder",
"from_date",
"to_date",
"from_time",
"to_time",
"year",
"label",
"has_raw",
"is_reference",
"keyword",
"label",
"max_size",
"min_size",
"name",
"person",
"query_eval",
"query_function",
"min_size",
"max_size",
"regex",
"selected",
"exif",
"duplicate",
"to_date",
"to_time",
"uti",
"uuid_from_file",
"uuid",
"year",
]
exclusive = [
("favorite", "not_favorite"),
("hidden", "not_hidden"),
("missing", "not_missing"),
("only_photos", "only_movies"),
("burst", "not_burst"),
("live", "not_live"),
("cloudasset", "not_cloudasset"),
("incloud", "not_incloud"),
("portrait", "not_portrait"),
("screenshot", "not_screenshot"),
("slow_mo", "not_slow_mo"),
("time_lapse", "not_time_lapse"),
("hdr", "not_hdr"),
("selfie", "not_selfie"),
("panorama", "not_panorama"),
("deleted", "deleted_only"),
("shared", "not_shared"),
("favorite", "not_favorite"),
("has_comment", "no_comment"),
("has_likes", "no_likes"),
("hdr", "not_hdr"),
("hidden", "not_hidden"),
("in_album", "not_in_album"),
("incloud", "not_incloud"),
("live", "not_live"),
("location", "no_location"),
("keyword", "no_keyword"),
("missing", "not_missing"),
("only_photos", "only_movies"),
("panorama", "not_panorama"),
("portrait", "not_portrait"),
("screenshot", "not_screenshot"),
("selfie", "not_selfie"),
("shared", "not_shared"),
("slow_mo", "not_slow_mo"),
("time_lapse", "not_time_lapse"),
]
# print help if no non-exclusive term or a double exclusive term is given
# TODO: add option to validate requiring at least one query arg
@@ -298,6 +302,7 @@ def _query_options_from_kwargs(**kwargs) -> QueryOptions:
all([any(kwargs["title"]), kwargs["no_title"]]),
all([any(kwargs["description"]), kwargs["no_description"]]),
all([any(kwargs["place"]), kwargs["no_place"]]),
all([any(kwargs["keyword"]), kwargs["no_keyword"]]),
]
):
raise IncompatibleQueryOptions

View File

@@ -0,0 +1,372 @@
"""Report writer for the --report option of `osxphotos export`"""
import csv
import json
import os
import os.path
import sqlite3
from abc import ABC, abstractmethod
from contextlib import suppress
from typing import Union, Dict
from osxphotos.photoexporter import ExportResults
from osxphotos.export_db import OSXPHOTOS_ABOUT_STRING
__all__ = [
"report_writer_factory",
"ReportWriterABC",
"ReportWriterCSV",
"ReportWriterSqlite",
"ReportWriterNoOp",
]
class ReportWriterABC(ABC):
"""Abstract base class for report writers"""
@abstractmethod
def write(self, export_results: ExportResults):
"""Write results to the output file"""
pass
@abstractmethod
def close(self):
"""Close the output file"""
pass
class ReportWriterNoOp(ABC):
"""Report writer that does nothing"""
def __init__(self):
pass
def write(self, export_results: ExportResults):
"""Write results to the output file"""
pass
def close(self):
"""Close the output file"""
pass
class ReportWriterCSV(ReportWriterABC):
"""Write CSV report file"""
def __init__(
self, output_file: Union[str, bytes, os.PathLike], append: bool = False
):
self.output_file = output_file
self.append = append
report_columns = [
"datetime",
"filename",
"exported",
"new",
"updated",
"skipped",
"exif_updated",
"touched",
"converted_to_jpeg",
"sidecar_xmp",
"sidecar_json",
"sidecar_exiftool",
"missing",
"error",
"exiftool_warning",
"exiftool_error",
"extended_attributes_written",
"extended_attributes_skipped",
"cleanup_deleted_file",
"cleanup_deleted_directory",
"exported_album",
]
mode = "a" if append else "w"
self._output_fh = open(self.output_file, mode)
self._csv_writer = csv.DictWriter(self._output_fh, fieldnames=report_columns)
if not append:
self._csv_writer.writeheader()
def write(self, export_results: ExportResults):
"""Write results to the output file"""
all_results = prepare_results_for_writing(export_results)
for data in list(all_results.values()):
self._csv_writer.writerow(data)
self._output_fh.flush()
def close(self):
"""Close the output file"""
self._output_fh.close()
def __del__(self):
with suppress(Exception):
self._output_fh.close()
class ReportWriterJSON(ReportWriterABC):
"""Write JSON report file"""
def __init__(
self, output_file: Union[str, bytes, os.PathLike], append: bool = False
):
self.output_file = output_file
self.append = append
self.indent = 4
self._first_record_written = False
if append:
with open(self.output_file, "r") as fh:
existing_data = json.load(fh)
self._output_fh = open(self.output_file, "w")
self._output_fh.write("[")
for data in existing_data:
self._output_fh.write(json.dumps(data, indent=self.indent))
self._output_fh.write(",\n")
else:
self._output_fh = open(self.output_file, "w")
self._output_fh.write("[")
def write(self, export_results: ExportResults):
"""Write results to the output file"""
all_results = prepare_results_for_writing(export_results, bool_values=True)
for data in list(all_results.values()):
if self._first_record_written:
self._output_fh.write(",\n")
else:
self._first_record_written = True
self._output_fh.write(json.dumps(data, indent=self.indent))
self._output_fh.flush()
def close(self):
"""Close the output file"""
self._output_fh.write("]")
self._output_fh.close()
def __del__(self):
with suppress(Exception):
self.close()
class ReportWriterSQLite(ReportWriterABC):
"""Write sqlite report file"""
def __init__(
self, output_file: Union[str, bytes, os.PathLike], append: bool = False
):
self.output_file = output_file
self.append = append
if not append:
with suppress(FileNotFoundError):
os.unlink(self.output_file)
self._conn = sqlite3.connect(self.output_file)
self._create_tables()
def write(self, export_results: ExportResults):
"""Write results to the output file"""
all_results = prepare_results_for_writing(export_results)
for data in list(all_results.values()):
cursor = self._conn.cursor()
cursor.execute(
"INSERT INTO report "
"(datetime, filename, exported, new, updated, skipped, exif_updated, touched, converted_to_jpeg, sidecar_xmp, sidecar_json, sidecar_exiftool, missing, error, exiftool_warning, exiftool_error, extended_attributes_written, extended_attributes_skipped, cleanup_deleted_file, cleanup_deleted_directory, exported_album) "
"VALUES "
"(:datetime, :filename, :exported, :new, :updated, :skipped, :exif_updated, :touched, :converted_to_jpeg, :sidecar_xmp, :sidecar_json, :sidecar_exiftool, :missing, :error, :exiftool_warning, :exiftool_error, :extended_attributes_written, :extended_attributes_skipped, :cleanup_deleted_file, :cleanup_deleted_directory, :exported_album);",
data,
)
self._conn.commit()
def close(self):
"""Close the output file"""
self._conn.close()
def _create_tables(self):
c = self._conn.cursor()
c.execute(
"""
CREATE TABLE IF NOT EXISTS report (
datetime text,
filename text,
exported integer,
new integer,
updated integer,
skipped integer,
exif_updated integer,
touched integer,
converted_to_jpeg integer,
sidecar_xmp integer,
sidecar_json integer,
sidecar_exiftool integer,
missing integer,
error text,
exiftool_warning text,
exiftool_error text,
extended_attributes_written integer,
extended_attributes_skipped integer,
cleanup_deleted_file integer,
cleanup_deleted_directory integer,
exported_album text
)
"""
)
c.execute(
"""
CREATE TABLE IF NOT EXISTS about (
id INTEGER PRIMARY KEY,
about TEXT
);"""
)
c.execute(
"INSERT INTO about(about) VALUES (?);",
(f"OSXPhotos Export Report. {OSXPHOTOS_ABOUT_STRING}",),
)
self._conn.commit()
def __del__(self):
with suppress(Exception):
self.close()
def prepare_results_for_writing(
export_results: ExportResults, bool_values: bool = False
) -> Dict:
"""Return all results for writing to report
Args:
export_results: ExportResults object
bool_values: Return a boolean value instead of a integer (e.g. for use with JSON)
Returns:
Dict: All results
"""
false = False if bool_values else 0
true = True if bool_values else 1
all_results = {}
for result in (
export_results.all_files()
+ export_results.deleted_files
+ export_results.deleted_directories
):
result = str(result)
if result not in all_results:
all_results[str(result)] = {
"datetime": export_results.datetime,
"filename": str(result),
"exported": false,
"new": false,
"updated": false,
"skipped": false,
"exif_updated": false,
"touched": false,
"converted_to_jpeg": false,
"sidecar_xmp": false,
"sidecar_json": false,
"sidecar_exiftool": false,
"missing": false,
"error": "",
"exiftool_warning": "",
"exiftool_error": "",
"extended_attributes_written": false,
"extended_attributes_skipped": false,
"cleanup_deleted_file": false,
"cleanup_deleted_directory": false,
"exported_album": "",
}
for result in export_results.exported:
all_results[str(result)]["exported"] = true
for result in export_results.new:
all_results[str(result)]["new"] = true
for result in export_results.updated:
all_results[str(result)]["updated"] = true
for result in export_results.skipped:
all_results[str(result)]["skipped"] = true
for result in export_results.exif_updated:
all_results[str(result)]["exif_updated"] = true
for result in export_results.touched:
all_results[str(result)]["touched"] = true
for result in export_results.converted_to_jpeg:
all_results[str(result)]["converted_to_jpeg"] = true
for result in export_results.sidecar_xmp_written:
all_results[str(result)]["sidecar_xmp"] = true
all_results[str(result)]["exported"] = true
for result in export_results.sidecar_xmp_skipped:
all_results[str(result)]["sidecar_xmp"] = true
all_results[str(result)]["skipped"] = true
for result in export_results.sidecar_json_written:
all_results[str(result)]["sidecar_json"] = true
all_results[str(result)]["exported"] = true
for result in export_results.sidecar_json_skipped:
all_results[str(result)]["sidecar_json"] = true
all_results[str(result)]["skipped"] = true
for result in export_results.sidecar_exiftool_written:
all_results[str(result)]["sidecar_exiftool"] = true
all_results[str(result)]["exported"] = true
for result in export_results.sidecar_exiftool_skipped:
all_results[str(result)]["sidecar_exiftool"] = true
all_results[str(result)]["skipped"] = true
for result in export_results.missing:
all_results[str(result)]["missing"] = true
for result in export_results.error:
all_results[str(result[0])]["error"] = result[1]
for result in export_results.exiftool_warning:
all_results[str(result[0])]["exiftool_warning"] = result[1]
for result in export_results.exiftool_error:
all_results[str(result[0])]["exiftool_error"] = result[1]
for result in export_results.xattr_written:
all_results[str(result)]["extended_attributes_written"] = true
for result in export_results.xattr_skipped:
all_results[str(result)]["extended_attributes_skipped"] = true
for result in export_results.deleted_files:
all_results[str(result)]["cleanup_deleted_file"] = true
for result in export_results.deleted_directories:
all_results[str(result)]["cleanup_deleted_directory"] = true
for result, album in export_results.exported_album:
all_results[str(result)]["exported_album"] = album
return all_results
def report_writer_factory(
output_file: Union[str, bytes, os.PathLike], append: bool = False
) -> ReportWriterABC:
"""Return a ReportWriter instance appropriate for the output file type"""
output_type = os.path.splitext(output_file)[1]
output_type = output_type.lower()[1:]
if output_type == "csv":
return ReportWriterCSV(output_file, append)
elif output_type == "json":
return ReportWriterJSON(output_file, append)
elif output_type in ["sqlite", "db"]:
return ReportWriterSQLite(output_file, append)
else:
raise ValueError(f"Unknown report file type: {output_file}")

Binary file not shown.

View File

@@ -84,8 +84,7 @@ def terminate_exiftool():
@lru_cache(maxsize=1)
def get_exiftool_path():
"""return path of exiftool, cache result"""
exiftool_path = shutil.which("exiftool")
if exiftool_path:
if exiftool_path := shutil.which("exiftool"):
return exiftool_path.rstrip()
else:
raise FileNotFoundError(

View File

@@ -1,18 +1,21 @@
""" Helper class for managing database used by PhotoExporter for tracking state of exports and updates """
import contextlib
import datetime
import gzip
import json
import logging
import os
import pathlib
import pickle
import sqlite3
import sys
import time
from contextlib import suppress
from io import StringIO
from sqlite3 import Error
from tempfile import TemporaryDirectory
from typing import Optional, Tuple, Union
from typing import Any, Optional, Tuple, Union
from tenacity import retry, stop_after_attempt
@@ -27,12 +30,42 @@ __all__ = [
"ExportDBTemp",
]
OSXPHOTOS_EXPORTDB_VERSION = "6.0"
OSXPHOTOS_EXPORTDB_VERSION = "7.0"
OSXPHOTOS_ABOUT_STRING = f"Created by osxphotos version {__version__} (https://github.com/RhetTbull/osxphotos) on {datetime.datetime.now()}"
# max retry attempts for methods which use tenacity.retry
MAX_RETRY_ATTEMPTS = 5
# maximum number of export results rows to save
MAX_EXPORT_RESULTS_DATA_ROWS = 10
def pickle_and_zip(data: Any) -> bytes:
"""
Pickle and gzip data.
Args:
data: data to pickle and gzip (must be pickle-able)
Returns:
bytes of gzipped pickled data
"""
pickled = pickle.dumps(data)
return gzip.compress(pickled)
def unzip_and_unpickle(data: bytes) -> Any:
"""
Unzip and unpickle data.
Args:
data: data to unzip and unpickle
Returns:
unpickled data
"""
return pickle.loads(gzip.decompress(data))
class ExportDB:
"""Interface to sqlite3 database used to store state information for osxphotos export command"""
@@ -192,6 +225,63 @@ class ExportDB:
except Error as e:
logging.warning(e)
def set_export_results(self, results):
"""Store export results in database; data is pickled and gzipped for storage"""
results_data = pickle_and_zip(results)
conn = self._conn
try:
dt = datetime.datetime.now().isoformat()
c = conn.cursor()
c.execute(
"""
UPDATE export_results_data
SET datetime = ?,
export_results = ?
WHERE datetime = (SELECT MIN(datetime) FROM export_results_data);
""",
(dt, results_data),
)
conn.commit()
except Error as e:
logging.warning(e)
def get_export_results(self, run: int = 0):
"""Retrieve export results from database
Args:
run: which run to retrieve results for;
0 = most recent run, -1 = previous run, -2 = run prior to that, etc.
Returns:
ExportResults object or None if no results found
"""
if run > 0:
raise ValueError("run must be 0 or negative")
run = -run
conn = self._conn
try:
c = conn.cursor()
c.execute(
"""
SELECT export_results
FROM export_results_data
ORDER BY datetime DESC
""",
)
rows = c.fetchall()
try:
data = rows[run][0]
results = unzip_and_unpickle(data) if data else None
except IndexError:
results = None
except Error as e:
logging.warning(e)
results = None
return results
def close(self):
"""close the database connection"""
try:
@@ -362,12 +452,16 @@ class ExportDB:
# create export_data table
self._migrate_5_0_to_6_0(conn)
if version[1] < "7.0":
# create report_data table
self._migrate_6_0_to_7_0(conn)
conn.execute("VACUUM;")
conn.commit()
def __del__(self):
"""ensure the database connection is closed"""
with contextlib.suppress(Exception):
with suppress(Exception):
self._conn.close()
def _insert_run_info(self):
@@ -556,6 +650,29 @@ class ExportDB:
except Error as e:
logging.warning(e)
def _migrate_6_0_to_7_0(self, conn):
try:
c = conn.cursor()
c.execute(
"""CREATE TABLE IF NOT EXISTS export_results_data (
id INTEGER PRIMARY KEY,
datetime TEXT,
export_results BLOB
);"""
)
# pre-populate report_data table with blank fields
# ExportDB will use these as circular buffer always writing to the oldest record
for _ in range(MAX_EXPORT_RESULTS_DATA_ROWS):
c.execute(
"""INSERT INTO export_results_data (datetime, export_results) VALUES (?, ?);""",
(datetime.datetime.now().isoformat(), b""),
)
# sleep a tiny bit just to ensure time stamps increment
time.sleep(0.001)
conn.commit()
except Error as e:
logging.warning(e)
def _perform_db_maintenace(self, conn):
"""Perform database maintenance"""
try:
@@ -630,7 +747,7 @@ class ExportDBInMemory(ExportDB):
except Error as e:
logging.warning(e)
def _open_export_db(self, dbfile):
def _open_export_db(self, dbfile): # sourcery skip: raise-specific-error
"""open export database and return a db connection
returns: connection to the database
"""
@@ -681,7 +798,7 @@ class ExportDBInMemory(ExportDB):
def __del__(self):
"""close the database connection"""
with contextlib.suppress(Error):
with suppress(Error):
self.close()
@@ -933,6 +1050,10 @@ class ExportRecord:
"photoinfo": photoinfo,
}
def json(self, indent=None):
"""Return json of self"""
return json.dumps(self.asdict(), indent=indent)
def __enter__(self):
self._context_manager = True
return self

View File

@@ -2,16 +2,15 @@
"""
import dataclasses
import hashlib
import json
import logging
import os
import pathlib
import re
import tempfile
import typing as t
from collections import namedtuple # pylint: disable=syntax-error
from dataclasses import asdict, dataclass
from datetime import datetime
from enum import Enum
import photoscript
@@ -45,14 +44,13 @@ from .photokit import (
from .phototemplate import RenderOptions
from .rich_utils import add_rich_markup_tag
from .uti import get_preferred_uti_extension
from .utils import increment_filename, lineno, list_directory
from .utils import hexdigest, increment_filename, lineno, list_directory
__all__ = [
"ExportError",
"ExportOptions",
"ExportResults",
"PhotoExporter",
"hexdigest",
"rename_jpeg_files",
]
@@ -241,57 +239,62 @@ class ExportResults:
def __init__(
self,
exported=None,
new=None,
updated=None,
skipped=None,
exif_updated=None,
touched=None,
to_touch=None,
converted_to_jpeg=None,
sidecar_json_written=None,
sidecar_json_skipped=None,
sidecar_exiftool_written=None,
sidecar_exiftool_skipped=None,
sidecar_xmp_written=None,
sidecar_xmp_skipped=None,
missing=None,
error=None,
exiftool_warning=None,
exiftool_error=None,
xattr_written=None,
xattr_skipped=None,
deleted_files=None,
deleted_directories=None,
deleted_files=None,
error=None,
exif_updated=None,
exiftool_error=None,
exiftool_warning=None,
exported_album=None,
skipped_album=None,
exported=None,
metadata_changed=None,
missing_album=None,
missing=None,
new=None,
sidecar_exiftool_skipped=None,
sidecar_exiftool_written=None,
sidecar_json_skipped=None,
sidecar_json_written=None,
sidecar_xmp_skipped=None,
sidecar_xmp_written=None,
skipped_album=None,
skipped=None,
to_touch=None,
touched=None,
updated=None,
xattr_skipped=None,
xattr_written=None,
):
self.exported = exported or []
self.new = new or []
self.updated = updated or []
self.skipped = skipped or []
self.exif_updated = exif_updated or []
self.touched = touched or []
self.to_touch = to_touch or []
self.datetime = datetime.now().isoformat()
self.converted_to_jpeg = converted_to_jpeg or []
self.sidecar_json_written = sidecar_json_written or []
self.sidecar_json_skipped = sidecar_json_skipped or []
self.sidecar_exiftool_written = sidecar_exiftool_written or []
self.sidecar_exiftool_skipped = sidecar_exiftool_skipped or []
self.sidecar_xmp_written = sidecar_xmp_written or []
self.sidecar_xmp_skipped = sidecar_xmp_skipped or []
self.missing = missing or []
self.error = error or []
self.exiftool_warning = exiftool_warning or []
self.exiftool_error = exiftool_error or []
self.xattr_written = xattr_written or []
self.xattr_skipped = xattr_skipped or []
self.deleted_files = deleted_files or []
self.deleted_directories = deleted_directories or []
self.deleted_files = deleted_files or []
self.error = error or []
self.exif_updated = exif_updated or []
self.exiftool_error = exiftool_error or []
self.exiftool_warning = exiftool_warning or []
self.exported = exported or []
self.exported_album = exported_album or []
self.skipped_album = skipped_album or []
self.metadata_changed = metadata_changed or []
self.missing = missing or []
self.missing_album = missing_album or []
self.new = new or []
self.sidecar_exiftool_skipped = sidecar_exiftool_skipped or []
self.sidecar_exiftool_written = sidecar_exiftool_written or []
self.sidecar_json_skipped = sidecar_json_skipped or []
self.sidecar_json_written = sidecar_json_written or []
self.sidecar_xmp_skipped = sidecar_xmp_skipped or []
self.sidecar_xmp_written = sidecar_xmp_written or []
self.skipped = skipped or []
self.skipped_album = skipped_album or []
self.to_touch = to_touch or []
self.touched = touched or []
self.updated = updated or []
self.xattr_skipped = xattr_skipped or []
self.xattr_written = xattr_written or []
def all_files(self):
"""return all filenames contained in results"""
@@ -342,13 +345,15 @@ class ExportResults:
self.exported_album += other.exported_album
self.skipped_album += other.skipped_album
self.missing_album += other.missing_album
self.metadata_changed += other.metadata_changed
return self
def __str__(self):
return (
"ExportResults("
+ f"exported={self.exported}"
+ f"datetime={self.datetime}"
+ f",exported={self.exported}"
+ f",new={self.new}"
+ f",updated={self.updated}"
+ f",skipped={self.skipped}"
@@ -371,12 +376,14 @@ class ExportResults:
+ f",exported_album={self.exported_album}"
+ f",skipped_album={self.skipped_album}"
+ f",missing_album={self.missing_album}"
+ f",metadata_changed={self.metadata_changed}"
+ ")"
)
class PhotoExporter:
"""Export a photo"""
def __init__(self, photo: "PhotoInfo", tmpdir: t.Optional[str] = None):
self.photo = photo
self._render_options = RenderOptions()
@@ -739,7 +746,7 @@ class PhotoExporter:
return ShouldUpdate.EDITED_SIG_DIFFERENT
if options.force_update:
current_digest = hexdigest(self.photo.json())
current_digest = self.photo.hexdigest
if current_digest != file_record.digest:
# metadata in Photos changed, force update
return ShouldUpdate.DIGEST_DIFFERENT
@@ -1179,8 +1186,9 @@ class PhotoExporter:
rec.dest_sig = fileutil.file_sig(dest)
if options.exiftool:
rec.exifdata = self._exiftool_json_sidecar(options)
if options.force_update:
rec.digest = hexdigest(photoinfo)
if self.photo.hexdigest != rec.digest:
results.metadata_changed = [dest_str]
rec.digest = self.photo.hexdigest
return results
@@ -2011,13 +2019,6 @@ class PhotoExporter:
f.close()
def hexdigest(strval):
"""hexdigest of a string, using blake2b"""
h = hashlib.blake2b(digest_size=20)
h.update(bytes(strval, "utf-8"))
return h.hexdigest()
def _check_export_suffix(src, dest, edited):
"""Helper function for exporting photos to check file extensions of destination path.

View File

@@ -11,6 +11,7 @@ import os
import os.path
import pathlib
from datetime import timedelta, timezone
from functools import cached_property
from typing import Optional
import yaml
@@ -54,7 +55,7 @@ from .scoreinfo import ScoreInfo
from .searchinfo import SearchInfo
from .text_detection import detect_text
from .uti import get_preferred_uti_extension, get_uti_for_extension
from .utils import _get_resource_loc, list_directory
from .utils import _get_resource_loc, hexdigest, list_directory
__all__ = ["PhotoInfo", "PhotoInfoNone"]
@@ -892,38 +893,37 @@ class PhotoInfo:
return photopath
@property
@cached_property
def path_derivatives(self):
"""Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first)"""
try:
return self._path_derivatives
except AttributeError:
if self._db._db_version <= _PHOTOS_4_VERSION:
self._path_derivatives = self._path_derivatives_4()
return self._path_derivatives
if self._db._db_version <= _PHOTOS_4_VERSION:
return self._path_derivatives_4()
directory = self._uuid[0] # first char of uuid
derivative_path = (
pathlib.Path(self._db._library_path)
/ "resources"
/ "derivatives"
/ directory
)
files = derivative_path.glob(f"{self.uuid}*.*")
files = sorted(files, reverse=True, key=lambda f: f.stat().st_size)
# return list of filename but skip .THM files (these are actually low-res thumbnails in JPEG format but with .THM extension)
derivatives = [
str(filename) for filename in files if filename.suffix != ".THM"
]
if (
self.isphoto
and len(derivatives) > 1
and derivatives[0].endswith(".mov")
):
derivatives[1], derivatives[0] = derivatives[0], derivatives[1]
if self.shared:
return self._path_derivatives_5_shared()
self._path_derivatives = derivatives
return self._path_derivatives
directory = self._uuid[0] # first char of uuid
derivative_path = (
pathlib.Path(self._db._library_path) / f"resources/derivatives/{directory}"
)
files = list(derivative_path.glob(f"{self.uuid}*.*"))
# previews may be missing from derivatives path
# there are what appear to be low res thumbnails in the "masters" subfolder
thumb_path = (
pathlib.Path(self._db._library_path)
/ f"resources/derivatives/masters/{directory}/{self.uuid}_4_5005_c.jpeg"
)
if thumb_path.exists():
files.append(thumb_path)
files = sorted(files, reverse=True, key=lambda f: f.stat().st_size)
# return list of filename but skip .THM files (these are actually low-res thumbnails in JPEG format but with .THM extension)
derivatives = [str(filename) for filename in files if filename.suffix != ".THM"]
if self.isphoto and len(derivatives) > 1 and derivatives[0].endswith(".mov"):
derivatives[1], derivatives[0] = derivatives[0], derivatives[1]
return derivatives
def _path_derivatives_4(self):
"""Return paths to all derivative (preview) files for Photos <= 4"""
@@ -933,10 +933,7 @@ class PhotoInfo:
folder_id, file_id = _get_resource_loc(modelid)
derivatives_root = (
pathlib.Path(self._db._library_path)
/ "resources"
/ "proxies"
/ "derivatives"
/ folder_id
/ f"resources/proxies/derivatives/{folder_id}"
)
# photos appears to usually be in "00" subfolder but
@@ -961,6 +958,19 @@ class PhotoInfo:
# didn't find a derivatives path
return []
def _path_derivatives_5_shared(self):
"""Return paths to all derivative (preview) files for shared iCloud photos in Photos >= 5"""
directory = self._uuid[0] # first char of uuid
# only 1 derivative for shared photos and it's called 'UUID_4_5005_c.jpeg'
derivative_path = (
pathlib.Path(self._db._library_path)
/ "resources/cloudsharing/resources/derivatives/masters"
/ f"{directory}/{self.uuid}_4_5005_c.jpeg"
)
if derivative_path.exists():
return [str(derivative_path)]
return []
@property
def panorama(self):
"""Returns True if photo is a panorama, otherwise False"""
@@ -1356,6 +1366,12 @@ class PhotoInfo:
self._exiftool = exiftool
return self._exiftool
@cached_property
def hexdigest(self):
"""Returns a unique digest of the photo's properties and metadata;
useful for detecting changes in any property/metadata of the photo"""
return hexdigest(self.json())
def detected_text(self, confidence_threshold=TEXT_DETECTION_CONFIDENCE_THRESHOLD):
"""Detects text in photo and returns lists of results as (detected text, confidence)

View File

@@ -3076,6 +3076,8 @@ class PhotosDB:
photos = _get_photos_by_attribute(
photos, "keywords", keyword, options.ignore_case
)
elif options.no_keyword:
photos = [p for p in photos if not p.keywords]
if person:
photos = _get_photos_by_attribute(
@@ -3446,6 +3448,23 @@ class PhotosDB:
matching_photos.append(p)
photos = matching_photos
if options.added_after:
added_after = options.added_after
if not datetime_has_tz(added_after):
added_after = datetime_naive_to_local(added_after)
photos = [p for p in photos if p.date_added and p.date_added > added_after]
if options.added_before:
added_before = options.added_before
if not datetime_has_tz(added_before):
added_before = datetime_naive_to_local(added_before)
photos = [p for p in photos if p.date_added and p.date_added < added_before]
if options.added_in_last:
added_after = datetime.now() - options.added_in_last
added_after = datetime_naive_to_local(added_after)
photos = [p for p in photos if p.date_added and p.date_added > added_after]
if options.function:
for function in options.function:
photos = function[0](photos)

View File

@@ -1,13 +1,13 @@
""" Custom template system for osxphotos, implements metadata template language (MTL) """
import datetime
import json
import locale
import logging
import os
import pathlib
import shlex
import sys
from contextlib import suppress
from dataclasses import dataclass
from typing import Optional
@@ -419,7 +419,7 @@ class PhotoTemplate:
try:
model = self.parser.parse(template)
except TextXSyntaxError as e:
raise ValueError(f"SyntaxError: {e}")
raise ValueError(f"SyntaxError: {e}") from e
if not model:
# empty string
@@ -612,10 +612,12 @@ class PhotoTemplate:
break
if match:
break
if (match and not negation) or (negation and not match):
return ["True"]
else:
return []
return (
["True"]
if (match and not negation) or (negation and not match)
else []
)
def comparison_test(test_function):
"""Perform numerical comparisons using test_function; closure to capture conditional_val, vals, negation"""
@@ -627,14 +629,16 @@ class PhotoTemplate:
match = bool(
test_function(float(vals[0]), float(conditional_value[0]))
)
if (match and not negation) or (negation and not match):
return ["True"]
else:
return []
return (
["True"]
if (match and not negation) or (negation and not match)
else []
)
except ValueError as e:
raise ValueError(
f"comparison operators may only be used with values that can be converted to numbers: {vals} {conditional_value}"
)
) from e
if operator in ["contains", "matches", "startswith", "endswith"]:
# process any "or" values separated by "|"
@@ -722,9 +726,6 @@ class PhotoTemplate:
ValueError if no rule exists for field.
"""
if self.photo.uuid is None:
return []
# initialize today with current date/time if needed
if self.today is None:
self.today = datetime.datetime.now()
@@ -732,7 +733,50 @@ class PhotoTemplate:
value = None
# wouldn't a switch/case statement be nice...
if field == "name":
# handle the fields that don't require a PhotoInfo object first
if field == "today.date":
value = DateTimeFormatter(self.today).date
elif field == "today.year":
value = DateTimeFormatter(self.today).year
elif field == "today.yy":
value = DateTimeFormatter(self.today).yy
elif field == "today.mm":
value = DateTimeFormatter(self.today).mm
elif field == "today.month":
value = DateTimeFormatter(self.today).month
elif field == "today.mon":
value = DateTimeFormatter(self.today).mon
elif field == "today.dd":
value = DateTimeFormatter(self.today).dd
elif field == "today.dow":
value = DateTimeFormatter(self.today).dow
elif field == "today.doy":
value = DateTimeFormatter(self.today).doy
elif field == "today.hour":
value = DateTimeFormatter(self.today).hour
elif field == "today.min":
value = DateTimeFormatter(self.today).min
elif field == "today.sec":
value = DateTimeFormatter(self.today).sec
elif field == "today.strftime":
if default:
try:
value = self.today.strftime(default[0])
except:
raise ValueError(f"Invalid strftime template: '{default}'")
else:
value = None
elif field in PUNCTUATION:
value = PUNCTUATION[field]
elif field == "osxphotos_version":
value = __version__
elif field == "osxphotos_cmd_line":
value = " ".join(sys.argv)
elif self.photo.uuid is None:
# if no uuid, don't have a PhotoInfo object (could be PhotoInfoNone)
# so don't try to handle any of the photo fields
return []
elif field == "name":
value = pathlib.Path(self.photo.filename).stem
elif field == "original_name":
value = pathlib.Path(self.photo.original_filename).stem
@@ -865,38 +909,6 @@ class PhotoTemplate:
raise ValueError(f"Invalid strftime template: '{default}'")
else:
value = None
elif field == "today.date":
value = DateTimeFormatter(self.today).date
elif field == "today.year":
value = DateTimeFormatter(self.today).year
elif field == "today.yy":
value = DateTimeFormatter(self.today).yy
elif field == "today.mm":
value = DateTimeFormatter(self.today).mm
elif field == "today.month":
value = DateTimeFormatter(self.today).month
elif field == "today.mon":
value = DateTimeFormatter(self.today).mon
elif field == "today.dd":
value = DateTimeFormatter(self.today).dd
elif field == "today.dow":
value = DateTimeFormatter(self.today).dow
elif field == "today.doy":
value = DateTimeFormatter(self.today).doy
elif field == "today.hour":
value = DateTimeFormatter(self.today).hour
elif field == "today.min":
value = DateTimeFormatter(self.today).min
elif field == "today.sec":
value = DateTimeFormatter(self.today).sec
elif field == "today.strftime":
if default:
try:
value = self.today.strftime(default[0])
except:
raise ValueError(f"Invalid strftime template: '{default}'")
else:
value = None
elif field == "place.name":
value = self.photo.place.name if self.photo.place else None
elif field == "place.country_code":
@@ -982,33 +994,25 @@ class PhotoTemplate:
elif field == "id":
value = format_str_value(self.photo._info["pk"], subfield)
elif field.startswith("album_seq") or field.startswith("folder_album_seq"):
dest_path = self.dest_path
if not dest_path:
value = None
else:
if dest_path := self.dest_path:
if field.startswith("album_seq"):
album = pathlib.Path(dest_path).name
album_info = _get_album_by_name(self.photo, album)
else:
album_info = _get_album_by_path(self.photo, dest_path)
value = album_info.photo_index(self.photo) if album_info else None
else:
value = None
if value is not None:
try:
with suppress(IndexError):
start_id = field.split(".", 1)
value = int(value) + int(start_id[1])
except IndexError:
pass
value = format_str_value(value, subfield)
elif field in PUNCTUATION:
value = PUNCTUATION[field]
elif field == "osxphotos_version":
value = __version__
elif field == "osxphotos_cmd_line":
value = " ".join(sys.argv)
else:
# if here, didn't get a match
raise ValueError(f"Unhandled template value: {field}")
# sanitize filename or directory name if needed
if self.filename:
value = sanitize_pathpart(value)
elif self.dirname:
@@ -1038,8 +1042,8 @@ class PhotoTemplate:
field_value = None
try:
field_value = getattr(self, field_stem)
except AttributeError:
raise ValueError(f"Unknown path-like field: {field_stem}")
except AttributeError as e:
raise ValueError(f"Unknown path-like field: {field_stem}") from e
value = _get_pathlib_value(field, field_value, self.quote)
@@ -1083,14 +1087,14 @@ class PhotoTemplate:
value = ["{" + values + "}"] if values else []
elif filter_ == "parens":
if values and type(values) == list:
value = ["(" + v + ")" for v in values]
value = [f"({v})" for v in values]
else:
value = ["(" + values + ")"] if values else []
value = [f"({values})"] if values else []
elif filter_ == "brackets":
if values and type(values) == list:
value = ["[" + v + "]" for v in values]
value = [f"[{v}]" for v in values]
else:
value = ["[" + values + "]"] if values else []
value = [f"[{values}]"] if values else []
elif filter_ == "shell_quote":
if values and type(values) == list:
value = [shlex.quote(v) for v in values]
@@ -1406,7 +1410,7 @@ def get_template_field_table():
*TEMPLATE_SUBSTITUTIONS_MULTI_VALUED.items(),
]:
# replace '|' with '\|' to avoid markdown parsing issues (e.g. in {pipe} description)
descr = descr.replace("'|'", "'\|'")
descr = descr.replace("'|'", r"'\|'")
template_table += f"\n|{subst}|{descr}|"
return template_table

View File

@@ -15,158 +15,165 @@ class QueryOptions:
"""QueryOptions class for PhotosDB.query
Attributes:
keyword: list of keywords to search for
person: list of person names to search for
added_after: search for photos added after a given date
added_before: search for photos added before a given date
added_in_last: search for photos added in last X datetime.timedelta
album: list of album names to search for
folder: list of folder names to search for
uuid: list of uuids to search for
title: list of titles to search for
no_title: search for photos with no title
burst_photos: search for burst photos
burst: search for burst photos
cloudasset: search for photos that are managed by iCloud
deleted_only: search only for deleted photos
deleted: also include deleted photos
description: list of descriptions to search for
no_description: search for photos with no description
ignore_case: ignore case when searching
duplicate: search for duplicate photos
edited: search for edited photos
exif: search for photos with EXIF tags that matches the given data
external_edit: search for photos edited in external apps
favorite: search for favorite photos
not_favorite: search for non-favorite photos
hidden: search for hidden photos
not_hidden: search for non-hidden photos
missing: search for missing photos
not_missing: search for non-missing photos
shared: search for shared photos
not_shared: search for non-shared photos
photos: search for photos
movies: search for movies
uti: list of UTIs to search for
burst: search for burst photos
not_burst: search for non-burst photos
live: search for live photos
not_live: search for non-live photos
cloudasset: search for photos that are managed by iCloud
not_cloudasset: search for photos that are not managed by iCloud
incloud: search for cloud assets that are synched to iCloud
not_incloud: search for cloud asset photos that are not yet synched to iCloud
folder: list of folder names to search for
from_date: search for photos taken on or after this date
to_date: search for photos taken on or before this date
portrait: search for portrait photos
not_portrait: search for non-portrait photos
screenshot: search for screenshot photos
not_screenshot: search for non-screenshot photos
slow_mo: search for slow-mo photos
not_slow_mo: search for non-slow-mo photos
time_lapse: search for time-lapse photos
not_time_lapse: search for non-time-lapse photos
hdr: search for HDR photos
not_hdr: search for non-HDR photos
selfie: search for selfie photos
not_selfie: search for non-selfie photos
panorama: search for panorama photos
not_panorama: search for non-panorama photos
has_raw: search for photos with associated raw files
place: list of place names to search for
no_place: search for photos with no place
label: list of labels to search for
deleted: also include deleted photos
deleted_only: search only for deleted photos
has_comment: search for photos with comments
no_comment: search for photos with no comments
has_likes: search for shared photos with likes
no_likes: search for shared photos with no likes
is_reference: search for photos stored by reference (that is, they are not managed by Photos)
in_album: search for photos in an album
not_in_album: search for photos not in an album
burst_photos: search for burst photos
missing_bursts: for burst photos, also include burst photos that are missing
name: list of names to search for
min_size: minimum size of photos to search for
max_size: maximum size of photos to search for
regex: list of regular expressions to search for
query_eval: list of query expressions to evaluate
duplicate: search for duplicate photos
location: search for photos with a location
no_location: search for photos with no location
function: list of query functions to evaluate
has_comment: search for photos with comments
has_likes: search for shared photos with likes
has_raw: search for photos with associated raw files
hdr: search for HDR photos
hidden: search for hidden photos
ignore_case: ignore case when searching
in_album: search for photos in an album
incloud: search for cloud assets that are synched to iCloud
is_reference: search for photos stored by reference (that is, they are not managed by Photos)
keyword: list of keywords to search for
label: list of labels to search for
live: search for live photos
location: search for photos with a location
max_size: maximum size of photos to search for
min_size: minimum size of photos to search for
missing_bursts: for burst photos, also include burst photos that are missing
missing: search for missing photos
movies: search for movies
name: list of names to search for
no_comment: search for photos with no comments
no_description: search for photos with no description
no_likes: search for shared photos with no likes
no_location: search for photos with no location
no_keyword: search for photos with no keywords
no_place: search for photos with no place
no_title: search for photos with no title
not_burst: search for non-burst photos
not_cloudasset: search for photos that are not managed by iCloud
not_favorite: search for non-favorite photos
not_hdr: search for non-HDR photos
not_hidden: search for non-hidden photos
not_in_album: search for photos not in an album
not_incloud: search for cloud asset photos that are not yet synched to iCloud
not_live: search for non-live photos
not_missing: search for non-missing photos
not_panorama: search for non-panorama photos
not_portrait: search for non-portrait photos
not_screenshot: search for non-screenshot photos
not_selfie: search for non-selfie photos
not_shared: search for non-shared photos
not_slow_mo: search for non-slow-mo photos
not_time_lapse: search for non-time-lapse photos
panorama: search for panorama photos
person: list of person names to search for
photos: search for photos
place: list of place names to search for
portrait: search for portrait photos
query_eval: list of query expressions to evaluate
regex: list of regular expressions to search for
screenshot: search for screenshot photos
selected: search for selected photos
exif: search for photos with EXIF tags that matches the given data
selfie: search for selfie photos
shared: search for shared photos
slow_mo: search for slow-mo photos
time_lapse: search for time-lapse photos
title: list of titles to search for
to_date: search for photos taken on or before this date
uti: list of UTIs to search for
uuid: list of uuids to search for
year: search for photos taken in a given year
"""
keyword: Optional[Iterable[str]] = None
person: Optional[Iterable[str]] = None
added_after: Optional[datetime.datetime] = None
added_before: Optional[datetime.datetime] = None
added_in_last: Optional[datetime.timedelta] = None
album: Optional[Iterable[str]] = None
folder: Optional[Iterable[str]] = None
uuid: Optional[Iterable[str]] = None
title: Optional[Iterable[str]] = None
no_title: Optional[bool] = None
burst_photos: Optional[bool] = None
burst: Optional[bool] = None
cloudasset: Optional[bool] = None
deleted_only: Optional[bool] = None
deleted: Optional[bool] = None
description: Optional[Iterable[str]] = None
no_description: Optional[bool] = None
ignore_case: Optional[bool] = None
duplicate: Optional[bool] = None
edited: Optional[bool] = None
exif: Optional[Iterable[Tuple[str, str]]] = None
external_edit: Optional[bool] = None
favorite: Optional[bool] = None
not_favorite: Optional[bool] = None
hidden: Optional[bool] = None
not_hidden: Optional[bool] = None
missing: Optional[bool] = None
not_missing: Optional[bool] = None
shared: Optional[bool] = None
not_shared: Optional[bool] = None
photos: Optional[bool] = True
movies: Optional[bool] = True
uti: Optional[Iterable[str]] = None
burst: Optional[bool] = None
not_burst: Optional[bool] = None
live: Optional[bool] = None
not_live: Optional[bool] = None
cloudasset: Optional[bool] = None
not_cloudasset: Optional[bool] = None
incloud: Optional[bool] = None
not_incloud: Optional[bool] = None
folder: Optional[Iterable[str]] = None
from_date: Optional[datetime.datetime] = None
to_date: Optional[datetime.datetime] = None
from_time: Optional[datetime.time] = None
to_time: Optional[datetime.time] = None
portrait: Optional[bool] = None
not_portrait: Optional[bool] = None
screenshot: Optional[bool] = None
not_screenshot: Optional[bool] = None
slow_mo: Optional[bool] = None
not_slow_mo: Optional[bool] = None
time_lapse: Optional[bool] = None
not_time_lapse: Optional[bool] = None
hdr: Optional[bool] = None
not_hdr: Optional[bool] = None
selfie: Optional[bool] = None
not_selfie: Optional[bool] = None
panorama: Optional[bool] = None
not_panorama: Optional[bool] = None
has_raw: Optional[bool] = None
place: Optional[Iterable[str]] = None
no_place: Optional[bool] = None
label: Optional[Iterable[str]] = None
deleted: Optional[bool] = None
deleted_only: Optional[bool] = None
has_comment: Optional[bool] = None
no_comment: Optional[bool] = None
has_likes: Optional[bool] = None
no_likes: Optional[bool] = None
is_reference: Optional[bool] = None
in_album: Optional[bool] = None
not_in_album: Optional[bool] = None
burst_photos: Optional[bool] = None
missing_bursts: Optional[bool] = None
name: Optional[Iterable[str]] = None
min_size: Optional[bitmath.Byte] = None
max_size: Optional[bitmath.Byte] = None
regex: Optional[Iterable[Tuple[str, str]]] = None
query_eval: Optional[Iterable[str]] = None
duplicate: Optional[bool] = None
location: Optional[bool] = None
no_location: Optional[bool] = None
function: Optional[List[Tuple[callable, str]]] = None
has_comment: Optional[bool] = None
has_likes: Optional[bool] = None
has_raw: Optional[bool] = None
hdr: Optional[bool] = None
hidden: Optional[bool] = None
ignore_case: Optional[bool] = None
in_album: Optional[bool] = None
incloud: Optional[bool] = None
is_reference: Optional[bool] = None
keyword: Optional[Iterable[str]] = None
label: Optional[Iterable[str]] = None
live: Optional[bool] = None
location: Optional[bool] = None
max_size: Optional[bitmath.Byte] = None
min_size: Optional[bitmath.Byte] = None
missing_bursts: Optional[bool] = None
missing: Optional[bool] = None
movies: Optional[bool] = True
name: Optional[Iterable[str]] = None
no_comment: Optional[bool] = None
no_description: Optional[bool] = None
no_likes: Optional[bool] = None
no_location: Optional[bool] = None
no_keyword: Optional[bool] = None
no_place: Optional[bool] = None
no_title: Optional[bool] = None
not_burst: Optional[bool] = None
not_cloudasset: Optional[bool] = None
not_favorite: Optional[bool] = None
not_hdr: Optional[bool] = None
not_hidden: Optional[bool] = None
not_in_album: Optional[bool] = None
not_incloud: Optional[bool] = None
not_live: Optional[bool] = None
not_missing: Optional[bool] = None
not_panorama: Optional[bool] = None
not_portrait: Optional[bool] = None
not_screenshot: Optional[bool] = None
not_selfie: Optional[bool] = None
not_shared: Optional[bool] = None
not_slow_mo: Optional[bool] = None
not_time_lapse: Optional[bool] = None
panorama: Optional[bool] = None
person: Optional[Iterable[str]] = None
photos: Optional[bool] = True
place: Optional[Iterable[str]] = None
portrait: Optional[bool] = None
query_eval: Optional[Iterable[str]] = None
regex: Optional[Iterable[Tuple[str, str]]] = None
screenshot: Optional[bool] = None
selected: Optional[bool] = None
exif: Optional[Iterable[Tuple[str, str]]] = None
selfie: Optional[bool] = None
shared: Optional[bool] = None
slow_mo: Optional[bool] = None
time_lapse: Optional[bool] = None
title: Optional[Iterable[str]] = None
to_date: Optional[datetime.datetime] = None
to_time: Optional[datetime.time] = None
uti: Optional[Iterable[str]] = None
uuid: Optional[Iterable[str]] = None
year: Optional[Iterable[int]] = None
def asdict(self):

View File

@@ -8,7 +8,7 @@ import re
def utc_offset_string_to_seconds(utc_offset: str) -> int:
"""match a UTC offset in format ±[hh]:[mm], ±[h]:[mm], or ±[hh][mm] and return number of seconds offset"""
patterns = ["^([+-]?)(\d{1,2}):(\d{2})$", "^([+-]?)(\d{2})(\d{2})$"]
patterns = [r"^([+-]?)(\d{1,2}):(\d{2})$", r"^([+-]?)(\d{2})(\d{2})$"]
for pattern in patterns:
match = re.match(pattern, utc_offset)
if not match:
@@ -56,7 +56,7 @@ def update_datetime(
return dt
def time_string_to_datetime(time: str) -> datetime.datetime:
def time_string_to_datetime(time: str) -> datetime.time:
"""Convert time string to datetime.datetime"""
""" valid time formats:
@@ -74,7 +74,7 @@ def time_string_to_datetime(time: str) -> datetime.datetime:
for dt_format in time_formats:
try:
parsed_dt = datetime.datetime.strptime(time, dt_format)
parsed_dt = datetime.datetime.strptime(time, dt_format).time()
except ValueError as e:
pass
else:

View File

@@ -54,3 +54,8 @@ class Timezone:
def __repr__(self):
return self.name
def __eq__(self, other):
if isinstance(other, Timezone):
return self.timezone == other.timezone
return False

View File

@@ -158,7 +158,7 @@ Photos tracks a tremendous amount of metadata associated with photos in the libr
`osxphotos export /path/to/export --exiftool`
This will write basic metadata such as keywords, persons, and GPS location to the exported files. osxphotos includes several additional options that can be used in conjunction with `--exiftool` to modify the metadata that is written by `exiftool`. For example, you can use the `--keyword-template` option to specify custom keywords (again, via the osxphotos template system). For example, to use the folder and album a photo is in to create hierarchal keywords in the format used by Lightroom Classic:
This will write basic metadata such as keywords, persons, and GPS location to the exported files. osxphotos includes several additional options that can be used in conjunction with `--exiftool` to modify the metadata that is written by `exiftool`. For example, you can use the `--keyword-template` option to specify custom keywords (again, via the osxphotos template system). For example, to use the folder and album a photo is in to create hierarchical keywords in the format used by Lightroom Classic:
osxphotos export /path/to/export --exiftool --keyword-template "{folder_album(>)}"
│ │
@@ -170,7 +170,7 @@ This will write basic metadata such as keywords, persons, and GPS location to th
for joining the folders and albums. For example,
if photo is in Folder1/Folder2/Album, (>) produces
"Folder1>Folder2>Album" which some programs, such as
Lightroom Classic, treat as hierarchal keywords
Lightroom Classic, treat as hierarchical keywords
The above command will write all the regular metadata that `--exiftool` normally writes to the file upon export but will also add an additional keyword in the exported metadata in the form "Folder1>Folder2>Album". If you did not include the `(>)` in the template string (e.g. `{folder_album}`), folder_album would render in form "Folder1/Folder2/Album".
@@ -252,6 +252,10 @@ You can also export photos in a certain date range:
`osxphotos export /path/to/export --from-date "2020-01-01" --to-date "2020-02-28"`
or photos added to the library in the last week:
`osxphotos export /path/to/export --added-in-last "1 week"`
## Converting images to JPEG on export
Photos can store images in many different formats. osxphotos can convert non-JPEG images (for example, RAW photos) to JPEG on export using the `--convert-to-jpeg` option. You can specify the JPEG quality (0: worst, 1.0: best) using `--jpeg-quality`. For example:

View File

@@ -3,6 +3,7 @@
import datetime
import fnmatch
import glob
import hashlib
import importlib
import inspect
import logging
@@ -29,6 +30,7 @@ __all__ = [
"expand_and_validate_filepath",
"get_last_library_path",
"get_system_library_path",
"hexdigest",
"increment_filename_with_count",
"increment_filename",
"lineno",
@@ -514,6 +516,13 @@ def get_latest_version() -> Tuple[Optional[str], str]:
return None, e
def pluralize(count, singular, plural):
def pluralize(count: Optional[int], singular: str, plural: str) -> str:
"""Return singular or plural based on count"""
return singular if count == 1 else plural
def hexdigest(strval: str) -> str:
"""hexdigest of a string, using blake2b"""
h = hashlib.blake2b(digest_size=20)
h.update(bytes(strval, "utf-8"))
return h.hexdigest()

View File

@@ -19,13 +19,14 @@ pyobjc-framework-Metal>=7.3,<9.0
pyobjc-framework-Photos>=7.3,<9.0
pyobjc-framework-Quartz>=7.3,<9.0
pyobjc-framework-Vision>=7.3,<9.0
pytimeparse==1.1.8
pytimeparse2==1.4.0
PyYAML>=5.4.1,<6.0.0
requests>=2.27.1,<3.0.0
rich>=11.2.0,<13.0.0
rich_theme_manager>=0.11.0
rich>=11.2.0,<13.0.0
tenacity>=8.0.1,<9.0.0
textx>=2.3.0,<2.4.0
toml>=0.10.2,<0.11.0
wrapt>=1.13.3,<1.14.0
wurlitzer>=2.1.0,<2.2.0
xdg==5.1.1

View File

@@ -95,7 +95,7 @@ setup(
"pyobjc-framework-Photos>=7.3,<9.0",
"pyobjc-framework-Quartz>=7.3,<9.0",
"pyobjc-framework-Vision>=7.3,<9.0",
"pytimeparse==1.1.8",
"pytimeparse2==1.4.0",
"requests>=2.27.1,<3.0.0",
"rich>=11.2.0,<13.0.0",
"rich_theme_manager>=0.11.0",
@@ -104,6 +104,7 @@ setup(
"toml>=0.10.2,<0.11.0",
"wrapt>=1.13.3,<1.14.0",
"wurlitzer>=2.1.0,<3.0.0",
"xdg==5.1.1",
],
entry_points={"console_scripts": ["osxphotos=osxphotos.__main__:cli_main"]},
include_package_data=True,

View File

@@ -12,8 +12,12 @@ from osxphotos.exiftool import _ExifToolProc
from .test_catalina_10_15_7 import UUID_DICT_LOCAL
# run timewarp tests (configured with --timewarp)
TEST_TIMEWARP = False
# don't clean up crash logs (configured with --no-cleanup)
NO_CLEANUP = False
def get_os_version():
import platform
@@ -68,6 +72,12 @@ def pytest_addoption(parser):
parser.addoption(
"--timewarp", action="store_true", default=False, help="run --timewarp tests"
)
parser.addoption(
"--no-cleanup",
action="store_true",
default=False,
help="don't clean up crash logs after tests",
)
def pytest_configure(config):
@@ -81,10 +91,15 @@ def pytest_configure(config):
"markers", "timewarp: mark test as requiring --timewarp to run"
)
# this is hacky but I can't figure out how to check config options in other fixtures
if config.getoption("--timewarp"):
global TEST_TIMEWARP
TEST_TIMEWARP = True
if config.getoption("--no-cleanup"):
global NO_CLEANUP
NO_CLEANUP = True
def pytest_collection_modifyitems(config, items):
if not (config.getoption("--addalbum") and TEST_LIBRARY is not None):
@@ -163,7 +178,7 @@ def delete_crash_logs():
"""Delete left over crash logs from tests that were supposed to crash"""
yield
path = pathlib.Path(os.getcwd()) / "osxphotos_crash.log"
if path.is_file():
if path.is_file() and not NO_CLEANUP:
path.unlink()

View File

@@ -519,6 +519,7 @@ def test_path_derivatives(photosdb):
derivs = [
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_100_o.jpeg",
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_105_c.jpeg",
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_4_5005_c.jpeg",
]
for i, p in enumerate(path):
assert p.endswith(derivs[i])

View File

@@ -625,6 +625,7 @@ def test_path_derivatives(photosdb):
derivs = [
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_100_o.jpeg",
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_105_c.jpeg",
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_4_5005_c.jpeg",
]
for i, p in enumerate(path):
assert p.endswith(derivs[i])

View File

@@ -1,5 +1,5 @@
""" Test the command line interface (CLI) """
import csv
import datetime
import glob
import json
@@ -10,7 +10,6 @@ import pathlib
import re
import shutil
import sqlite3
import sys
import tempfile
import time
from tempfile import TemporaryDirectory
@@ -21,6 +20,7 @@ from osxmetadata import OSXMetaData, Tag
import osxphotos
from osxphotos._constants import OSXPHOTOS_EXPORT_DB
from osxphotos._version import __version__
from osxphotos.cli import (
about,
albums,
@@ -960,6 +960,18 @@ EXPORT_UNICODE_TITLE_FILENAMES = [
"Frítest (3).jpg",
]
# data for --report
UUID_REPORT = [
{
"uuid": "4D521201-92AC-43E5-8F7C-59BC41C37A96",
"filenames": ["IMG_1997.JPG", "IMG_1997.cr2"],
},
{
"uuid": "7783E8E6-9CAC-40F3-BE22-81FB7051C266",
"filenames": ["IMG_3092.heic", "IMG_3092_edited.jpeg"],
},
]
# data for --exif
QUERY_EXIF_DATA = [("EXIF:Make", "FUJIFILM", ["6191423D-8DB8-4D4C-92BE-9BBBA308AAC4"])]
QUERY_EXIF_DATA_CASE_INSENSITIVE = [
@@ -2712,6 +2724,27 @@ def test_query_keyword_4():
assert len(json_got) == 6
def test_query_no_keyword():
"""Test query --no-keyword"""
runner = CliRunner()
cwd = os.getcwd()
result = runner.invoke(
query,
[
"--json",
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--no-keyword",
"--added-before",
"2022-05-05",
],
)
assert result.exit_code == 0
json_got = json.loads(result.output)
assert len(json_got) == 11
def test_query_person_1():
"""Test query --person"""
@@ -5452,13 +5485,239 @@ def test_export_report():
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
# test report creation
result = runner.invoke(
export,
[os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V", "--report", "report.csv"],
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--uuid",
UUID_REPORT[0]["uuid"],
"--report",
"report.csv",
],
)
assert result.exit_code == 0
assert "Writing export report" in result.output
assert "Wrote export report" in result.output
assert os.path.exists("report.csv")
with open("report.csv", "r") as f:
reader = csv.DictReader(f)
rows = list(reader)
filenames = [str(pathlib.Path(row["filename"]).name) for row in rows]
assert sorted(filenames) == sorted(UUID_REPORT[0]["filenames"])
# test report gets overwritten
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--uuid",
UUID_REPORT[1]["uuid"],
"--report",
"report.csv",
],
)
assert result.exit_code == 0
with open("report.csv", "r") as f:
reader = csv.DictReader(f)
rows = list(reader)
filenames = [str(pathlib.Path(row["filename"]).name) for row in rows]
assert sorted(filenames) == sorted(UUID_REPORT[1]["filenames"])
# test report with --append
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--uuid",
UUID_REPORT[0]["uuid"],
"--report",
"report.csv",
"--overwrite",
"--append",
],
)
assert result.exit_code == 0
with open("report.csv", "r") as f:
reader = csv.DictReader(f)
rows = list(reader)
filenames = [str(pathlib.Path(row["filename"]).name) for row in rows]
assert sorted(filenames) == sorted(
UUID_REPORT[0]["filenames"] + UUID_REPORT[1]["filenames"]
)
def test_export_report_json():
"""test export with --report option for JSON report"""
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
# test report creation
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--uuid",
UUID_REPORT[0]["uuid"],
"--report",
"report.json",
],
)
assert result.exit_code == 0
assert "Wrote export report" in result.output
assert os.path.exists("report.json")
with open("report.json", "r") as f:
rows = json.load(f)
filenames = [str(pathlib.Path(row["filename"]).name) for row in rows]
assert sorted(filenames) == sorted(UUID_REPORT[0]["filenames"])
# test report gets overwritten
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--uuid",
UUID_REPORT[1]["uuid"],
"--report",
"report.json",
],
)
assert result.exit_code == 0
with open("report.json", "r") as f:
rows = json.load(f)
filenames = [str(pathlib.Path(row["filename"]).name) for row in rows]
assert sorted(filenames) == sorted(UUID_REPORT[1]["filenames"])
# test report with --append
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--uuid",
UUID_REPORT[0]["uuid"],
"--report",
"report.json",
"--overwrite",
"--append",
],
)
assert result.exit_code == 0
with open("report.json", "r") as f:
rows = json.load(f)
filenames = [str(pathlib.Path(row["filename"]).name) for row in rows]
assert sorted(filenames) == sorted(
UUID_REPORT[0]["filenames"] + UUID_REPORT[1]["filenames"]
)
@pytest.mark.parametrize("report_file", ["report.db", "report.sqlite"])
def test_export_report_sqlite(report_file):
"""test export with --report option with sqlite report"""
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
# test report creation
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--uuid",
UUID_REPORT[0]["uuid"],
"--report",
report_file,
],
)
assert result.exit_code == 0
assert "Wrote export report" in result.output
assert os.path.exists(report_file)
conn = sqlite3.connect(report_file)
c = conn.cursor()
c.execute("SELECT filename FROM report")
filenames = [str(pathlib.Path(row[0]).name) for row in c.fetchall()]
assert sorted(filenames) == sorted(UUID_REPORT[0]["filenames"])
# test report gets overwritten
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--uuid",
UUID_REPORT[1]["uuid"],
"--report",
report_file,
],
)
assert result.exit_code == 0
conn = sqlite3.connect(report_file)
c = conn.cursor()
c.execute("SELECT filename FROM report")
filenames = [str(pathlib.Path(row[0]).name) for row in c.fetchall()]
assert sorted(filenames) == sorted(UUID_REPORT[1]["filenames"])
# test report with --append
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--uuid",
UUID_REPORT[0]["uuid"],
"--report",
report_file,
"--overwrite",
"--append",
],
)
assert result.exit_code == 0
conn = sqlite3.connect(report_file)
c = conn.cursor()
c.execute("SELECT filename FROM report")
filenames = [str(pathlib.Path(row[0]).name) for row in c.fetchall()]
assert sorted(filenames) == sorted(
UUID_REPORT[0]["filenames"] + UUID_REPORT[1]["filenames"]
)
def test_export_report_template():
"""test export with --report option with a template for report name"""
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",
"--report",
"report_{osxphotos_version}.csv",
],
)
assert result.exit_code == 0
assert "Wrote export report" in result.output
assert os.path.exists(f"report_{__version__}.csv")
def test_export_report_not_a_file():
@@ -5472,7 +5731,7 @@ def test_export_report_not_a_file():
export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V", "--report", "."]
)
assert result.exit_code != 0
assert "report is a directory, must be file name" in result.output
assert "is a directory, must be file name" in result.output
def test_export_as_hardlink_download_missing():
@@ -7079,6 +7338,67 @@ def test_query_function():
assert json_got[0]["original_filename"] == "DSC03584.dng"
def test_query_added_after():
"""test query --added-after"""
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
results = runner.invoke(
query,
[
os.path.join(cwd, PHOTOS_DB_15_7),
"--json",
"--added-after",
"2022-02-03",
],
)
assert results.exit_code == 0
json_got = json.loads(results.output)
assert len(json_got) == 4
def test_query_added_before():
"""test query --added-before"""
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
results = runner.invoke(
query,
[
os.path.join(cwd, PHOTOS_DB_15_7),
"--json",
"--added-before",
"2019-07-28",
],
)
assert results.exit_code == 0
json_got = json.loads(results.output)
assert len(json_got) == 7
def test_query_added_in_last():
"""test query --added-in-last"""
# Note: ideally, I'd test this with freezegun to verify the time deltas worked but
# freezegun causes osxphotos tests to crash so we just test that the --added-in-last runs without error
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
results = runner.invoke(
query,
[
os.path.join(cwd, PHOTOS_DB_15_7),
"--json",
"--added-in-last",
"10 years",
],
)
assert results.exit_code == 0
def test_export_export_dir_template():
"""Test {export_dir} template"""
@@ -7592,3 +7912,144 @@ def test_theme_list():
result = runner.invoke(cli_main, ["theme", "--list"])
assert result.exit_code == 0
assert "Dark" in result.output
def test_export_added_after():
"""test export --added-after"""
runner = CliRunner()
cwd = os.getcwd()
with runner.isolated_filesystem():
result = runner.invoke(
export,
[
".",
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--added-after",
"2022-02-03",
],
)
assert result.exit_code == 0
assert "Exporting 4 photos" in result.output
def test_export_added_before():
"""test export --added-before"""
runner = CliRunner()
cwd = os.getcwd()
with runner.isolated_filesystem():
result = runner.invoke(
export,
[
".",
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--added-before",
"2019-07-28",
],
)
assert result.exit_code == 0
assert "Exporting 7 photos" in result.output
def test_export_added_in_last():
"""test export --added-in-last"""
# can't use freezegun as it causes osxphotos tests to crash so
# just run export with --added-in-last and verify no errors
runner = CliRunner()
cwd = os.getcwd()
with runner.isolated_filesystem():
result = runner.invoke(
export,
[
".",
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--added-in-last",
"10 years",
],
)
assert result.exit_code == 0
assert "Exporting" in result.output
def test_export_limit():
"""test export --limit"""
# Use --added-before so test doesn't break if photos added in the future
runner = CliRunner()
cwd = os.getcwd()
with runner.isolated_filesystem():
result = runner.invoke(
export,
[
".",
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--update",
"--limit",
"20",
"--added-before",
"2022-05-07",
],
)
assert result.exit_code == 0
assert "limit: 20/20 exported" in result.output
result = runner.invoke(
export,
[
".",
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--update",
"--limit",
"20",
"--added-before",
"2022-05-07",
],
)
assert result.exit_code == 0
assert "limit: 5/20 exported" in result.output
result = runner.invoke(
export,
[
".",
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--update",
"--limit",
"20",
"--added-before",
"2022-05-07",
],
)
assert result.exit_code == 0
assert "limit: 0/20 exported" in result.output
def test_export_no_keyword():
"""test export --no-keyword"""
runner = CliRunner()
cwd = os.getcwd()
with runner.isolated_filesystem():
result = runner.invoke(
export,
[
".",
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--no-keyword",
"--added-before",
"2022-05-05",
],
)
assert result.exit_code == 0
assert "Exporting 11" in result.output

View File

@@ -0,0 +1,205 @@
""" Test custom click paramater types used by osxphotos CLI"""
import datetime
from bitmath import MB
import pytest
from osxphotos.timezones import Timezone
from click.exceptions import BadParameter
from osxphotos.cli.param_types import (
BitMathSize,
DateOffset,
DateTimeISO8601,
ExportDBType,
FunctionCall,
TemplateString,
TimeISO8601,
TimeOffset,
TimeString,
UTCOffset,
)
def test_date_offset():
"""Test DateOffset"""
date_offset_data = {
"1": datetime.timedelta(days=1),
"1 day": datetime.timedelta(days=1),
"+1 day": datetime.timedelta(days=1),
"1 d": datetime.timedelta(days=1),
"1d": datetime.timedelta(days=1),
"+ 1": datetime.timedelta(days=1),
"+1 day": datetime.timedelta(days=1),
"+ 1": datetime.timedelta(days=1),
"14:30": datetime.timedelta(minutes=14, seconds=30),
"14:30:00": datetime.timedelta(hours=14, minutes=30, seconds=0),
"1 week": datetime.timedelta(days=7),
"2 wk": datetime.timedelta(days=14),
"2 months": datetime.timedelta(days=60),
"1 mos": datetime.timedelta(days=30),
}
for date_offset_str, date_offset_delta in date_offset_data.items():
assert DateOffset().convert(date_offset_str, None, None) == date_offset_delta
def test_date_offset_invalid_format():
"""Test DateOffset with invalid format"""
date_offset_data = [
"1 foo",
"1 day 14",
"day",
]
for date_offset_str in date_offset_data:
with pytest.raises(BadParameter):
DateOffset().convert(date_offset_str, None, None)
def test_time_offset():
"""Test TimeOffset"""
time_offset_data = {
"1": datetime.timedelta(seconds=1),
"1s": datetime.timedelta(seconds=1),
"2 s": datetime.timedelta(seconds=2),
"2 sec": datetime.timedelta(seconds=2),
"3 sec": datetime.timedelta(seconds=3),
"1m": datetime.timedelta(minutes=1),
"1 min": datetime.timedelta(minutes=1),
"1 day": datetime.timedelta(days=1),
"14:30": datetime.timedelta(minutes=14, seconds=30),
"14:30:00": datetime.timedelta(hours=14, minutes=30, seconds=0),
}
for time_offset_str, time_offset_delta in time_offset_data.items():
assert TimeOffset().convert(time_offset_str, None, None) == time_offset_delta
def test_time_offset_invalid_format():
"""Test TimeOffset with invalid format"""
time_offset_data = [
"1 foo",
"1 day 14",
"1 sec 1",
"sec",
]
for time_offset_str in time_offset_data:
with pytest.raises(BadParameter):
TimeOffset().convert(time_offset_str, None, None)
def test_bitmath_size():
"""Test BitMathSize"""
bitmath_size_data = {
"1048576": MB(1.048576),
"1.048576MB": MB(1.048576),
"1 MiB": MB(1.048576),
}
for bitmath_size_str, bitmath_size_int in bitmath_size_data.items():
assert BitMathSize().convert(bitmath_size_str, None, None) == bitmath_size_int
def test_bitmath_size_invalid_format():
"""Test BitMathSize with invalid format"""
bitmath_size_data = [
"1 foo",
"1 mehgabite",
]
for bitmath_size_str in bitmath_size_data:
with pytest.raises(BadParameter):
BitMathSize().convert(bitmath_size_str, None, None)
def test_date_time_iso8601():
"""Test DateTimeISO8601"""
date_time_iso8601_data = {
"2020-01-01T00:00:00": datetime.datetime(2020, 1, 1, 0, 0, 0),
"2020-01-01T00:00:00.000": datetime.datetime(2020, 1, 1, 0, 0, 0),
"2020-01-01": datetime.datetime(2020, 1, 1),
}
for date_time_iso8601_str, date_time_iso8601_dt in date_time_iso8601_data.items():
assert (
DateTimeISO8601().convert(date_time_iso8601_str, None, None)
== date_time_iso8601_dt
)
def test_date_time_iso8601_invalid_format():
"""Test DateTimeISO8601 with invalid format"""
date_time_iso8601_data = [
"20-01-01T00:00:00",
"20-01-1",
]
for date_time_iso8601_str in date_time_iso8601_data:
with pytest.raises(BadParameter):
DateTimeISO8601().convert(date_time_iso8601_str, None, None)
def test_time_iso8601():
"""Test TimeISO8601"""
time_iso8601_data = {
"00:00:00": datetime.time(0, 0, 0),
"00:00:00.000": datetime.time(0, 0, 0),
}
for time_iso8601_str, time_iso8601_dt in time_iso8601_data.items():
assert TimeISO8601().convert(time_iso8601_str, None, None) == time_iso8601_dt
def test_time_iso8601_invalid_format():
"""Test TimeISO8601 with invalid format"""
date_time_iso8601_data = [
"20-01-01T00:00:00",
"20-01-1",
]
for date_time_iso8601_str in date_time_iso8601_data:
with pytest.raises(BadParameter):
TimeISO8601().convert(date_time_iso8601_str, None, None)
def test_timestring():
"""Test TimeString"""
timestring_data = {
"01:02:03": datetime.time(1, 2, 3),
"01:02:03.000": datetime.time(1, 2, 3),
"01:02:03.0000": datetime.time(1, 2, 3),
"01:02": datetime.time(1, 2, 0),
}
for timestring_str, timestring_dt in timestring_data.items():
assert TimeString().convert(timestring_str, None, None) == timestring_dt
def test_timestring_invalid_format():
"""Test TimeString with invalid format"""
timestring_data = [
"20-01-01T00:00:00",
"20-01-1",
]
for timestring_str in timestring_data:
with pytest.raises(BadParameter):
TimeString().convert(timestring_str, None, None)
def test_utcoffset():
"""Test UTCOffset"""
utcoffset_data = {
"+00:00": Timezone(0),
"-00:00": Timezone(-0),
"+01:00": Timezone(3600),
"-01:00": Timezone(-3600),
"+01:30": Timezone(5400),
"-01:30": Timezone(-5400),
}
for utcoffset_str, utcoffset_int in utcoffset_data.items():
assert UTCOffset().convert(utcoffset_str, None, None) == utcoffset_int
def test_utcoffset_invalid_format():
"""Test UTCOffset with invalid format"""
utcoffset_data = [
"20-01-01T00:00:00",
"20-01-1",
]
for utcoffset_str in utcoffset_data:
with pytest.raises(BadParameter):
UTCOffset().convert(utcoffset_str, None, None)

View File

@@ -25,6 +25,7 @@ if OS_VER == "15":
from tests.config_timewarp_catalina import CATALINA_PHOTOS_5 as TEST_DATA
else:
pytest.skip(allow_module_level=True)
TEST_DATA = {}
@@ -81,7 +82,7 @@ def test_inspect(photoslib, suspend_capture, output_file):
runner = CliRunner()
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
@@ -101,6 +102,7 @@ def test_date(photoslib, suspend_capture):
"--date",
TEST_DATA["date"]["value"],
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
@@ -122,13 +124,14 @@ def test_date_delta(photoslib, suspend_capture, input_value, expected, output_fi
"--date-delta",
input_value,
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_inspect_output(output_file)
@@ -148,6 +151,7 @@ def test_time(photoslib, suspend_capture, input_value, expected, output_file):
"--time",
input_value,
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
@@ -156,7 +160,7 @@ def test_time(photoslib, suspend_capture, input_value, expected, output_file):
# don't use photo.date as it will return local time instead of the time in the timezone
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_inspect_output(output_file)
@@ -176,13 +180,14 @@ def test_time_delta(photoslib, suspend_capture, input_value, expected, output_fi
"--time-delta",
input_value,
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_inspect_output(output_file)
@@ -206,13 +211,14 @@ def test_time_zone(
"--timezone",
input_value,
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_inspect_output(output_file)
@@ -232,6 +238,7 @@ def test_compare_exif(photoslib, suspend_capture, expected, output_file):
[
"--compare-exif",
"--plain",
"--force",
"-o",
output_file,
],
@@ -258,6 +265,7 @@ def test_compare_exif_add_to_album(photoslib, suspend_capture, expected, album):
"--add-to-album",
album,
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
@@ -282,7 +290,7 @@ def test_compare_exif_3(photoslib, suspend_capture, expected, output_file):
runner = CliRunner()
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
@@ -304,13 +312,14 @@ def test_match(photoslib, suspend_capture, input_value, expected, output_file):
input_value,
"--match-time",
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_inspect_output(output_file)
@@ -324,7 +333,9 @@ def test_push_exif_missing_file():
runner = CliRunner()
result = runner.invoke(
timewarp, ["--push-exif", "--plain", "--verbose"], terminal_width=TERMINAL_WIDTH
timewarp,
["--push-exif", "--plain", "--force", "--verbose"],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
assert "Skipping EXIF update for missing photo" in result.output
@@ -361,6 +372,7 @@ def test_push_exif_1(
time_delta_value,
"--push-exif",
"--plain",
"--force",
]
if match:
cli_args.append("--match-time")
@@ -370,7 +382,7 @@ def test_push_exif_1(
assert result.exit_code == 0
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_inspect_output(output_file)
@@ -403,7 +415,7 @@ def test_push_exif_2(photoslib, suspend_capture, output_file):
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -414,6 +426,7 @@ def test_push_exif_2(photoslib, suspend_capture, output_file):
[
"--push-exif",
"--plain",
"--force",
"--verbose",
],
terminal_width=TERMINAL_WIDTH,
@@ -422,7 +435,7 @@ def test_push_exif_2(photoslib, suspend_capture, output_file):
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -442,14 +455,14 @@ def test_pull_exif_1(photoslib, suspend_capture, output_file):
# update the photo so we know if the data is updated
result = runner.invoke(
timewarp,
["-z", "-0400", "-D", "+1 day", "-m", "-V", "--plain"],
["-z", "-0400", "-D", "+1 day", "-m", "-V", "--plain", "--force"],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -460,6 +473,7 @@ def test_pull_exif_1(photoslib, suspend_capture, output_file):
[
"--pull-exif",
"--plain",
"--force",
"--verbose",
],
terminal_width=TERMINAL_WIDTH,
@@ -468,7 +482,7 @@ def test_pull_exif_1(photoslib, suspend_capture, output_file):
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -493,7 +507,7 @@ def test_pull_exif_no_time(photoslib, suspend_capture, output_file):
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -504,6 +518,7 @@ def test_pull_exif_no_time(photoslib, suspend_capture, output_file):
[
"--pull-exif",
"--plain",
"--force",
"--verbose",
],
terminal_width=TERMINAL_WIDTH,
@@ -512,7 +527,7 @@ def test_pull_exif_no_time(photoslib, suspend_capture, output_file):
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -537,7 +552,7 @@ def test_pull_exif_no_offset(photoslib, suspend_capture, output_file):
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -548,6 +563,7 @@ def test_pull_exif_no_offset(photoslib, suspend_capture, output_file):
[
"--pull-exif",
"--plain",
"--force",
"--verbose",
],
terminal_width=TERMINAL_WIDTH,
@@ -556,7 +572,7 @@ def test_pull_exif_no_offset(photoslib, suspend_capture, output_file):
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -583,7 +599,7 @@ def test_pull_exif_no_data(photoslib, suspend_capture, output_file):
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -594,6 +610,7 @@ def test_pull_exif_no_data(photoslib, suspend_capture, output_file):
[
"--pull-exif",
"--plain",
"--force",
"--verbose",
],
terminal_width=TERMINAL_WIDTH,
@@ -603,7 +620,7 @@ def test_pull_exif_no_data(photoslib, suspend_capture, output_file):
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -622,7 +639,7 @@ def test_pull_exif_no_data_use_file_time(photoslib, suspend_capture, output_file
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -633,6 +650,7 @@ def test_pull_exif_no_data_use_file_time(photoslib, suspend_capture, output_file
[
"--pull-exif",
"--plain",
"--force",
"--verbose",
"--use-file-time",
],
@@ -643,7 +661,7 @@ def test_pull_exif_no_data_use_file_time(photoslib, suspend_capture, output_file
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -668,6 +686,7 @@ def test_video_compare_exif(photoslib, suspend_capture, expected, output_file):
[
"--compare-exif",
"--plain",
"--force",
"-o",
output_file,
],
@@ -695,6 +714,7 @@ def test_video_date_delta(
"--date-delta",
input_value,
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
@@ -702,7 +722,7 @@ def test_video_date_delta(
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_inspect_output(output_file)
@@ -726,13 +746,14 @@ def test_video_time_delta(
"--time-delta",
input_value,
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_inspect_output(output_file)
@@ -752,6 +773,7 @@ def test_video_date(photoslib, suspend_capture, input_value, expected, output_fi
"--date",
input_value,
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
@@ -760,7 +782,7 @@ def test_video_date(photoslib, suspend_capture, input_value, expected, output_fi
# don't use photo.date as it will return local time instead of the time in the timezone
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_inspect_output(output_file)
@@ -780,6 +802,7 @@ def test_video_time(photoslib, suspend_capture, input_value, expected, output_fi
"--time",
input_value,
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
@@ -788,7 +811,7 @@ def test_video_time(photoslib, suspend_capture, input_value, expected, output_fi
# don't use photo.date as it will return local time instead of the time in the timezone
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_inspect_output(output_file)
@@ -812,13 +835,14 @@ def test_video_time_zone(
"--timezone",
input_value,
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_inspect_output(output_file)
@@ -840,13 +864,14 @@ def test_video_match(photoslib, suspend_capture, input_value, expected, output_f
input_value,
"--match-time",
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_inspect_output(output_file)
@@ -865,7 +890,7 @@ def test_video_push_exif(photoslib, suspend_capture, output_file):
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -876,6 +901,7 @@ def test_video_push_exif(photoslib, suspend_capture, output_file):
[
"--push-exif",
"--plain",
"--force",
"--verbose",
],
terminal_width=TERMINAL_WIDTH,
@@ -884,7 +910,7 @@ def test_video_push_exif(photoslib, suspend_capture, output_file):
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -904,14 +930,25 @@ def test_video_pull_exif(photoslib, suspend_capture, output_file):
# update the photo so we know if the data is updated
result = runner.invoke(
timewarp,
["-z", "-0500", "-D", "+1 day", "-T", "-10 hours", "-m", "-V", "--plain"],
[
"-z",
"-0500",
"-D",
"+1 day",
"-T",
"-10 hours",
"-m",
"-V",
"--plain",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -922,6 +959,7 @@ def test_video_pull_exif(photoslib, suspend_capture, output_file):
[
"--pull-exif",
"--plain",
"--force",
"--verbose",
],
terminal_width=TERMINAL_WIDTH,
@@ -930,7 +968,7 @@ def test_video_pull_exif(photoslib, suspend_capture, output_file):
result = runner.invoke(
timewarp,
["--compare-exif", "--plain", "-o", output_file],
["--compare-exif", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_compare_exif(output_file)
@@ -956,13 +994,14 @@ def test_function(photoslib, suspend_capture, output_file):
[
"--function",
"tests/timewarp_function_example.py::get_date_time_timezone",
"--force",
],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
result = runner.invoke(
timewarp,
["--inspect", "--plain", "-o", output_file],
["--inspect", "--plain", "--force", "-o", output_file],
terminal_width=TERMINAL_WIDTH,
)
output_values = parse_inspect_output(output_file)

View File

@@ -0,0 +1,33 @@
# Test cloud photos
import pytest
import osxphotos
PHOTOS_DB_CLOUD = "tests/Test-Cloud-10.16.0.photoslibrary"
UUID_DICT = {
"incloud": "FC638F58-84BE-4083-B5DE-F85BDC729062",
"shared": "2094984A-21DC-4A6E-88A6-7344F648B92E",
"cloudasset": "FC638F58-84BE-4083-B5DE-F85BDC729062",
}
@pytest.fixture(scope="module")
def photosdb():
return osxphotos.PhotosDB(dbfile=PHOTOS_DB_CLOUD)
def test_incloud(photosdb):
photos = photosdb.photos(uuid=[UUID_DICT["incloud"]])
assert photos[0].incloud
def test_cloudasset_1(photosdb):
photos = photosdb.photos(uuid=[UUID_DICT["cloudasset"]])
assert photos[0].iscloudasset
def test_path_derivatives(photosdb):
photo = photosdb.get_photo(UUID_DICT["shared"])
assert photo.path_derivatives

View File

@@ -12,6 +12,7 @@ UUID_DICT = {
"not_incloud": "33000A44-E4BA-43A3-9304-62A0195AB4D9",
"cloudasset": "D11D25FF-5F31-47D2-ABA9-58418878DC15",
"not_cloudasset": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
"shared": "4AD7C8EF-2991-4519-9D3A-7F44A6F031BE",
}
@@ -52,3 +53,8 @@ def test_cloudasset_3():
photos = photosdb.photos(uuid=[UUID_DICT["not_cloudasset"]])
assert not photos[0].iscloudasset
def test_path_derivatives(photosdb):
photo = photosdb.get_photo(UUID_DICT["shared"])
assert photo.path_derivatives

View File

@@ -524,6 +524,7 @@ def test_path_derivatives(photosdb):
derivs = [
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_100_o.jpeg",
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_105_c.jpeg",
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_4_5005_c.jpeg",
]
for i, p in enumerate(path):
assert p.endswith(derivs[i])

View File

@@ -579,6 +579,7 @@ def test_path_derivatives(photosdb):
derivs = [
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_100_o.jpeg",
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_105_c.jpeg",
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_4_5005_c.jpeg",
]
for i, p in enumerate(path):
assert p.endswith(derivs[i])