Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51317a607c | ||
|
|
5f63cccc7c | ||
|
|
b3a935bd90 | ||
|
|
56435b101f | ||
|
|
04c2f6121a | ||
|
|
f47aa72165 | ||
|
|
561c6846e4 | ||
|
|
0d7e324f02 | ||
|
|
c2f02c3b7b | ||
|
|
04e1149cad | ||
|
|
bb7a81f9ed | ||
|
|
203dccb39f | ||
|
|
75568269bb | ||
|
|
9e9266ec9c | ||
|
|
6f1e81b038 | ||
|
|
0122f9b2d8 | ||
|
|
b6e7a75a81 | ||
|
|
43512240b3 | ||
|
|
a049b99b0e | ||
|
|
6c1650b7cf | ||
|
|
49821d377c | ||
|
|
175d7ea223 | ||
|
|
0a973d67f9 | ||
|
|
999b16e80f | ||
|
|
ed3473f602 | ||
|
|
8d020cbf09 | ||
|
|
dae710b836 | ||
|
|
1daf18ad9f | ||
|
|
7926c8d676 | ||
|
|
9d7a5e22d9 | ||
|
|
e9cc6ce137 | ||
|
|
bc32b1827f | ||
|
|
206ad8c33c | ||
|
|
65e8be7ed7 | ||
|
|
6477292a13 | ||
|
|
7a52d413a3 | ||
|
|
ab8b7b4b19 | ||
|
|
25d4459efd | ||
|
|
128e84c7a4 | ||
|
|
4771207595 | ||
|
|
0eca3b9c13 | ||
|
|
afec845e1b | ||
|
|
64002044d2 | ||
|
|
4e40d4b74e | ||
|
|
63d515646b | ||
|
|
0a7575b889 | ||
|
|
c776f3070d | ||
|
|
3b789242aa |
120
API_README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
In addition to a command line interface, OSXPhotos provides a access to a Python API that allows you to easily access a Photos library, often with just a few lines of code.
|
||||
|
||||
# Table of Contents
|
||||
## Table of Contents
|
||||
|
||||
* [Example uses of the Python package](#example-uses-of-the-python-package)
|
||||
* [Package Interface](#package-interface)
|
||||
@@ -12,6 +12,7 @@ In addition to a command line interface, OSXPhotos provides a access to a Python
|
||||
* [AlbumInfo](#albuminfo)
|
||||
* [ImportInfo](#importinfo)
|
||||
* [ProjectInfo](#projectinfo)
|
||||
* [MomentInfo](#momentinfo)
|
||||
* [FolderInfo](#folderinfo)
|
||||
* [PlaceInfo](#placeinfo)
|
||||
* [ScoreInfo](#scoreinfo)
|
||||
@@ -266,6 +267,10 @@ Returns a list of [ImportInfo](#importinfo) objects representing the import sess
|
||||
|
||||
Returns a list of [ProjectInfo](#projectinfo) objects representing the projects/creations (cards, calendars, etc.) in the database.
|
||||
|
||||
#### `moment_info`
|
||||
|
||||
Returns the [MomentInfo](#momentinfo) object for the photo or `None` if the photo does not have an associated moment.
|
||||
|
||||
#### `folder_info`
|
||||
|
||||
```python
|
||||
@@ -969,6 +974,10 @@ 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.
|
||||
@@ -1212,6 +1221,50 @@ Returns a list of [PhotoInfo](#PhotoInfo) objects representing each photo contai
|
||||
|
||||
Returns the creation date as a timezone aware datetime.datetime object of the project.
|
||||
|
||||
### MomentInfo
|
||||
|
||||
PhotoInfo.moment_info return the MomentInfo object for the photo. The MomentInfo object contains information about the photo's moment as assigned by Photos. The MomentInfo object contains the following properties:
|
||||
|
||||
#### `pk`
|
||||
|
||||
Returns the primary key of the moment in the Photos database.
|
||||
|
||||
#### `location`
|
||||
|
||||
Returns the location of the moment as a tuple of (latitude, longitude).
|
||||
|
||||
#### `title`
|
||||
|
||||
Returns the title of the moment.
|
||||
|
||||
#### `subtitle`
|
||||
|
||||
Returns the subtitle of the moment.
|
||||
|
||||
#### `start_date`
|
||||
|
||||
Returns the start date of the moment as a timezone aware datetime.datetime object.
|
||||
|
||||
#### `end_date`
|
||||
|
||||
Returns the end date of the moment as a timezone aware datetime.datetime object.
|
||||
|
||||
#### `date`
|
||||
|
||||
Returns the date of the moment as a timezone aware datetime.datetime object.
|
||||
|
||||
#### `modification_date`
|
||||
|
||||
Returns the modification date of the moment as a timezone aware datetime.datetime object.
|
||||
|
||||
#### `photos`
|
||||
|
||||
Returns a list of [PhotoInfo] objects representing the photos in the moment.
|
||||
|
||||
#### `asdict()`
|
||||
|
||||
Returns a dictionary representation of the moment.
|
||||
|
||||
### FolderInfo
|
||||
|
||||
PhotosDB.folder_info returns a list of FolderInfo objects representing the top level folders in the library. Each FolderInfo object represents a single folder in the Photos library.
|
||||
@@ -1573,18 +1626,6 @@ Returns list of x, y coordinates as tuples `[(x0, y0), (x1, y1)]` representing t
|
||||
|
||||
Coordinates as (x, y) tuple for the center of the detected face.
|
||||
|
||||
#### `mouth`
|
||||
|
||||
Coordinates as (x, y) tuple for the mouth of the detected face.
|
||||
|
||||
#### `left_eye`
|
||||
|
||||
Coordinates as (x, y) tuple for the left eye of the detected face.
|
||||
|
||||
#### `right_eye`
|
||||
|
||||
Coordinates as (x, y) tuple for the right eye of the detected face.
|
||||
|
||||
#### `size_pixels`
|
||||
|
||||
Diameter of detected face region in pixels.
|
||||
@@ -1601,29 +1642,25 @@ Roll of face region in radians.
|
||||
|
||||
Pitch of face region in radians.
|
||||
|
||||
**Note**: Only valid on Photos version <= 4, otherwise returns 0
|
||||
|
||||
#### yaw
|
||||
|
||||
Yaw of face region in radians.
|
||||
|
||||
**Note**: Only valid on Photos version <= 4, otherwise returns 0
|
||||
|
||||
#### `Additional properties`
|
||||
|
||||
The following additional properties are also available but are not yet fully documented.
|
||||
|
||||
* `center_x`: x coordinate of center of face in Photos' internal reference frame
|
||||
* `center_y`: y coordinate of center of face in Photos' internal reference frame
|
||||
* `mouth_x`: x coordinate of mouth in Photos' internal reference frame
|
||||
* `mouth_y`: y coordinate of mouth in Photos' internal reference frame
|
||||
* `left_eye_x`: x coordinate of left eye in Photos' internal reference frame
|
||||
* `left_eye_y`: y coordinate of left eye in Photos' internal reference frame
|
||||
* `right_eye_x`: x coordinate of right eye in Photos' internal reference frame
|
||||
* `right_eye_y`: y coordinate of right eye in Photos' internal reference frame
|
||||
* `size`: size of face region in Photos' internal reference frame
|
||||
* `quality`: quality measure of detected face
|
||||
* `source_width`: width in pixels of photo
|
||||
* `source_height`: height in pixels of photo
|
||||
* `has_smile`:
|
||||
* `left_eye_closed`:
|
||||
* `right_eye_closed`:
|
||||
* `manual`:
|
||||
* `face_type`:
|
||||
* `age_type`:
|
||||
@@ -1727,7 +1764,7 @@ In its simplest form, a template statement has the form: `"{template_field}"`, f
|
||||
|
||||
Template statements may contain one or more modifiers. The full syntax is:
|
||||
|
||||
`"pretext{delim+template_field:subfield|filter(path_sep)[find,replace] conditional?bool_value,default}posttext"`
|
||||
`"pretext{delim+template_field:subfield(field_arg)|filter[find,replace] conditional?bool_value,default}posttext"`
|
||||
|
||||
Template statements are white-space sensitive meaning that white space (spaces, tabs) changes the meaning of the template statement.
|
||||
|
||||
@@ -1748,6 +1785,8 @@ e.g. if Photo keywords are `["foo","bar"]`:
|
||||
|
||||
`:subfield`: Some templates have sub-fields, For example, `{exiftool:IPTC:Make}`; the template_field is `exiftool` and the sub-field is `IPTC:Make`.
|
||||
|
||||
`(field_arg)`: optional arguments to pass to the field; for example, with `{folder_album}` this is used to pass the path separator used for joining folders and albums when rendering the field (default is "/" for `{folder_album}`).
|
||||
|
||||
`|filter`: You may optionally append one or more filter commands to the end of the template field using the vertical pipe ('|') symbol. Filters may be combined, separated by '|' as in: `{keyword|capitalize|parens}`.
|
||||
|
||||
Valid filters are:
|
||||
@@ -1762,6 +1801,20 @@ Valid filters are:
|
||||
- `brackets`: Enclose value in brackets, e.g. 'value' => '[value]'
|
||||
- `shell_quote`: Quotes the value for safe usage in the shell, e.g. My file.jpeg => 'My file.jpeg'; only adds quotes if needed.
|
||||
- `function`: Run custom python function to filter value; use in format 'function:/path/to/file.py::function_name'. See example at https://github.com/RhetTbull/osxphotos/blob/master/examples/template_filter.py
|
||||
- `split(x)`: Split value into a list of values using x as delimiter, e.g. 'value1;value2' => ['value1', 'value2'] if used with split(;).
|
||||
- `autosplit`: Automatically split delimited string into separate values; will split strings delimited by comma, semicolon, or space, e.g. 'value1,value2' => ['value1', 'value2'].
|
||||
- `chop(x)`: Remove x characters off the end of value, e.g. chop(1): 'Value' => 'Valu'; when applied to a list, chops characters from each list value, e.g. chop(1): ['travel', 'beach']=> ['trave', 'beac'].
|
||||
- `chomp(x)`: Remove x characters from the beginning of value, e.g. chomp(1): ['Value'] => ['alue']; when applied to a list, removes characters from each list value, e.g. chomp(1): ['travel', 'beach']=> ['ravel', 'each'].
|
||||
- `sort`: Sort list of values, e.g. ['c', 'b', 'a'] => ['a', 'b', 'c'].
|
||||
- `rsort`: Sort list of values in reverse order, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
|
||||
- `reverse`: Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
|
||||
- `uniq`: Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b', 'c'].
|
||||
- `join(x)`: Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] => 'a,b,c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. e.g. join(): ['a', 'b', 'c'] => 'abc'.
|
||||
- `append(x)`: Append x to list of values, e.g. append(d): ['a', 'b', 'c'] => ['a', 'b', 'c', 'd'].
|
||||
- `prepend(x)`: Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] => ['d', 'a', 'b', 'c'].
|
||||
- `remove(x)`: Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] => ['a', 'c'].
|
||||
- `slice(start:stop:step)`: Slice list using same semantics as Python's list slicing, e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] => ['b', 'd']; slice(1:): ['a', 'b', 'c', 'd'] => ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c']; slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also sslice().
|
||||
- `sslice(start:stop:step)`: [s(tring) slice] Slice values in a list using same semantics as Python's string slicing, e.g. sslice(1:3):'abcd => 'bc'; sslice(1:4:2): 'abcd' => 'bd', etc. See also slice().
|
||||
|
||||
e.g. if Photo keywords are `["FOO","bar"]`:
|
||||
|
||||
@@ -1774,8 +1827,6 @@ e.g. if Photo description is "my description":
|
||||
|
||||
- `"{descr|titlecase}"` renders to: `"My Description"`
|
||||
|
||||
`(path_sep)`: optional path separator to use when joining path-like fields, for example `{folder_album}`. Default is "/".
|
||||
|
||||
e.g. If Photo is in `Album1` in `Folder1`:
|
||||
|
||||
- `"{folder_album}"` renders to `["Folder1/Album1"]`
|
||||
@@ -1818,7 +1869,7 @@ This can be used to rename files as well, for example:
|
||||
|
||||
This renames any photo that is a favorite as 'Favorite-ImageName.jpg' (where 'ImageName.jpg' is the original name of the photo) and all other photos with the unmodified original name.
|
||||
|
||||
`?bool_value`: Template fields may be evaluated as boolean (True/False) by appending "?" after the field name (and following "(path_sep)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is `"{hdr}"`) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is `"{title}"`) then the default value following a "," will be used.
|
||||
`?bool_value`: Template fields may be evaluated as boolean (True/False) by appending "?" after the field name (and following "(field_arg)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is `"{hdr}"`) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is `"{title}"`) then the default value following a "," will be used.
|
||||
|
||||
e.g. if photo is an HDR image,
|
||||
|
||||
@@ -1848,6 +1899,16 @@ Either or both bool_value or default (False value) may be empty which would resu
|
||||
If you want to include "{" or "}" in the output, use "{openbrace}" or "{closebrace}" template substitution.
|
||||
|
||||
e.g. `"{created.year}/{openbrace}{title}{closebrace}"` would result in `"2020/{Photo Title}"`.
|
||||
|
||||
**Variables**
|
||||
|
||||
You can define variables for later use in the template string using the format `{var:NAME,VALUE}`. Variables may then be referenced using the format `%NAME`. For example: `{var:foo,bar}` defines the variable `%foo` to have value `bar`. This can be useful if you want to re-use a complex template value in multiple places within your template string or for allowing the use of characters that would otherwise be prohibited in a template string. For example, the "pipe" (`|`) character is not allowed in a find/replace pair but you can get around this limitation like so: `{var:pipe,{pipe}}{title[-,%pipe]}` which replaces the `-` character with `|` (the value of `%pipe`).
|
||||
|
||||
Variables can also be referenced as fields in the template string, for example: `{var:year,created.year}{original_name}-{%year}`. In some cases, use of variables can make your template string more readable. Variables can be used as template fields, as values for filters, as values for conditional operations, or as default values. When used as a conditional value or default value, variables should be treated like any other field and enclosed in braces as conditional and default values are evaluated as template strings. For example: `{var:name,Katie}{person contains {%name}?{%name},Not-{%name}}`.
|
||||
|
||||
If you need to use a `%` (percent sign character), you can escape the percent sign by using `%%`. You can also use the `{percent}` template field where a template field is required. For example:
|
||||
|
||||
`{title[:,%%]}` replaces the `:` with `%` and `{title contains Foo?{title}{percent},{title}}` adds `%` to the title if it contains `Foo`.
|
||||
<!--[[[end]]] -->
|
||||
|
||||
The following template field substitutions are availabe for use the templating system.
|
||||
@@ -1926,9 +1987,10 @@ cog.out(get_template_field_table())
|
||||
|{exif.lens_model}|Lens model from original photo's EXIF information as imported by Photos, e.g. 'iPhone 6s back camera 4.15mm f/2.2'|
|
||||
|{moment}|The moment title of the photo|
|
||||
|{uuid}|Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'|
|
||||
|{shortuuid}|A shorter representation of photo's internal universally unique identifier (UUID) for the photo, a 22-character string unique to the photo, e.g. 'JYsxugP9UjetmCbBCHXcmu'|
|
||||
|{id}|A unique number for the photo based on its primary key in the Photos database. A sequential integer, e.g. 1, 2, 3...etc. Each asset associated with a photo (e.g. an image and Live Photo preview) will share the same id. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{id:05d}' which results in 00001, 00002, 00003...etc. |
|
||||
|{album_seq}|An integer, starting at 0, indicating the photo's index (sequence) in the containing album. Only valid when used in a '--filename' template and only when '{album}' or '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{album_seq}_{original_name}"'. To start counting at a value other than 0, append append a period and the starting value to the field name. For example, to start counting at 1 instead of 0: '{album_seq.1}'. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{album_seq:05d}' which results in 00000, 00001, 00002...etc. This may result in incorrect sequences if you have duplicate albums with the same name; see also '{folder_album_seq}'.|
|
||||
|{folder_album_seq}|An integer, starting at 0, indicating the photo's index (sequence) in the containing album and folder path. Only valid when used in a '--filename' template and only when '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{folder_album_seq}_{original_name}"'. To start counting at a value other than 0, append append a period and the starting value to the field name. For example, to start counting at 1 instead of 0: '{folder_album_seq.1}' May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{folder_album_seq:05d}' which results in 00000, 00001, 00002...etc. This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also '{album_seq}'.|
|
||||
|{album_seq}|An integer, starting at 0, indicating the photo's index (sequence) in the containing album. Only valid when used in a '--filename' template and only when '{album}' or '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{album_seq}_{original_name}"'. To start counting at a value other than 0, append append '(starting_value)' to the field name. For example, to start counting at 1 instead of 0: '{album_seq(1)}'. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{album_seq:05d}' which results in 00000, 00001, 00002...etc. To format while also using a starting value: '{album_seq:05d(1)}' which results in 0001, 00002...etc.This may result in incorrect sequences if you have duplicate albums with the same name; see also '{folder_album_seq}'.|
|
||||
|{folder_album_seq}|An integer, starting at 0, indicating the photo's index (sequence) in the containing album and folder path. Only valid when used in a '--filename' template and only when '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{folder_album_seq}_{original_name}"'. To start counting at a value other than 0, append '(starting_value)' to the field name. For example, to start counting at 1 instead of 0: '{folder_album_seq(1)}' May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{folder_album_seq:05d}' which results in 00000, 00001, 00002...etc. To format while also using a starting value: '{folder_album_seq:05d(1)}' which results in 0001, 00002...etc.This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also '{album_seq}'. |
|
||||
|{comma}|A comma: ','|
|
||||
|{semicolon}|A semicolon: ';'|
|
||||
|{questionmark}|A question mark: '?'|
|
||||
@@ -1943,7 +2005,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.49.2'|
|
||||
|{osxphotos_version}|The osxphotos version, e.g. '0.50.4'|
|
||||
|{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|
|
||||
@@ -1964,6 +2026,7 @@ cog.out(get_template_field_table())
|
||||
|{detected_text}|List of text strings found in the image after performing text detection. Using '{detected_text}' will cause osxphotos to perform text detection on your photos using the built-in macOS text detection algorithms which will slow down your export. The results for each photo will be cached in the export database so that future exports with '--update' do not need to reprocess each photo. You may pass a confidence threshold value between 0.0 and 1.0 after a colon as in '{detected_text:0.5}'; The default confidence threshold is 0.75. '{detected_text}' works only on macOS Catalina (10.15) or later. Note: this feature is not the same thing as Live Text in macOS Monterey, which osxphotos does not yet support.|
|
||||
|{shell_quote}|Use in form '{shell_quote,TEMPLATE}'; quotes the rendered TEMPLATE value(s) for safe usage in the shell, e.g. My file.jpeg => 'My file.jpeg'; only adds quotes if needed.|
|
||||
|{strip}|Use in form '{strip,TEMPLATE}'; strips whitespace from begining and end of rendered TEMPLATE value(s).|
|
||||
|{format}|Use in form, '{format:TYPE:FORMAT,TEMPLATE}'; converts TEMPLATE value to TYPE then formats the value using Python string formatting codes specified by FORMAT; TYPE is one of: 'int', 'float', or 'str'. For example, '{format:float:.1f,{exiftool:EXIF:FocalLength}}' will format focal length to 1 decimal place (e.g. '100.0'). |
|
||||
|{function}|Execute a python function from an external file and use return value as template substitution. Use in format: {function:file.py::function_name} where 'file.py' is the name of the python file and 'function_name' is the name of the function to call. The function will be passed the PhotoInfo object for the photo. See https://github.com/RhetTbull/osxphotos/blob/master/examples/template_function.py for an example of how to implement a template function.|
|
||||
<!--[[[end]]] -->
|
||||
|
||||
@@ -2112,6 +2175,7 @@ Attributes:
|
||||
* touched: list of files touched during export (e.g. file date/time updated with touch_file=True)
|
||||
* to_touch: Reserved for internal use of export
|
||||
* converted_to_jpeg: list of files converted to jpeg when convert_to_jpeg=True
|
||||
* metadata_changed: list of filenames that had metadata changes since last export
|
||||
* sidecar_json_written: list of JSON sidecars written
|
||||
* sidecar_json_skipped: list of JSON sidecars skipped when update=True
|
||||
* sidecar_exiftool_written: list of exiftool sidecars written
|
||||
|
||||
84
CHANGELOG.md
@@ -4,6 +4,90 @@ 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.50.2](https://github.com/RhetTbull/osxphotos/compare/v0.50.1...v0.50.2)
|
||||
|
||||
> 28 May 2022
|
||||
|
||||
- Added shortuuid, #314 [`7556826`](https://github.com/RhetTbull/osxphotos/commit/75568269bbd7b05a22f9fd00acd6f59691f1a507)
|
||||
- Added slice, sslice filters [`9e9266e`](https://github.com/RhetTbull/osxphotos/commit/9e9266ec9c890ed6fb09d61b1a075be954bef7c1)
|
||||
- Fixed shortuuid docs [`203dccb`](https://github.com/RhetTbull/osxphotos/commit/203dccb39fbe68b996d53126b52fd3fcedc5f0a1)
|
||||
|
||||
#### [v0.50.1](https://github.com/RhetTbull/osxphotos/compare/v0.50.0...v0.50.1)
|
||||
|
||||
> 28 May 2022
|
||||
|
||||
- Updated README.md, #707 [skip ci] [`a049b99`](https://github.com/RhetTbull/osxphotos/commit/a049b99b0ef67803da917f92ecb24b18fbe6a6c9)
|
||||
- Version 0.50.1 with --delete-file, --delete-uuid exportdb commands [`6c1650b`](https://github.com/RhetTbull/osxphotos/commit/6c1650b7cffefc223374f66012393f14d443fa72)
|
||||
- Updated docs [skip ci] [`b6e7a75`](https://github.com/RhetTbull/osxphotos/commit/b6e7a75a8110e7f71ae615e50e91419d92f5b59e)
|
||||
|
||||
#### [v0.50.0](https://github.com/RhetTbull/osxphotos/compare/v0.49.9...v0.50.0)
|
||||
|
||||
> 28 May 2022
|
||||
|
||||
- Version 0.50.0 with updated template engine [`175d7ea`](https://github.com/RhetTbull/osxphotos/commit/175d7ea223dcb650ad3f642b0ae23f0ed1cf2a37)
|
||||
- Updated template language to match autofile [`0a973d6`](https://github.com/RhetTbull/osxphotos/commit/0a973d67f94a5c59ee7dbbf4fb18892c61666d5d)
|
||||
|
||||
#### [v0.49.9](https://github.com/RhetTbull/osxphotos/compare/v0.49.8...v0.49.9)
|
||||
|
||||
> 26 May 2022
|
||||
|
||||
- Updated docs [`8d020cb`](https://github.com/RhetTbull/osxphotos/commit/8d020cbf09fcd2921cfb388a187bb6e891b263b2)
|
||||
- Implemented retry for export db, #569 [`dae710b`](https://github.com/RhetTbull/osxphotos/commit/dae710b836a8ff0d3553052d9d7f9af6e1a94f02)
|
||||
- Bug fix, #695 [`7926c8d`](https://github.com/RhetTbull/osxphotos/commit/7926c8d676d0ff38b60eda7c88409a932801f115)
|
||||
|
||||
#### [v0.49.8](https://github.com/RhetTbull/osxphotos/compare/v0.49.7...v0.49.8)
|
||||
|
||||
> 23 May 2022
|
||||
|
||||
- Removed screencast, [skip ci] [`9d7a5e2`](https://github.com/RhetTbull/osxphotos/commit/9d7a5e22d92cd889c2a54b4c58828f6e499995de)
|
||||
|
||||
#### [v0.49.7](https://github.com/RhetTbull/osxphotos/compare/v0.49.6...v0.49.7)
|
||||
|
||||
> 23 May 2022
|
||||
|
||||
- Bug fix, #695 [`e9cc6ce`](https://github.com/RhetTbull/osxphotos/commit/e9cc6ce137927664a42a3dfb1d93d7ba44d74e4d)
|
||||
- Removed screencast, [skip ci] [`bc32b18`](https://github.com/RhetTbull/osxphotos/commit/bc32b1827f55b4481a49ea2db0530e5a77719028)
|
||||
- Added screencast [skip ci] [`206ad8c`](https://github.com/RhetTbull/osxphotos/commit/206ad8c33c04e4b3074e8b32942266cb043aebd4)
|
||||
|
||||
#### [v0.49.6](https://github.com/RhetTbull/osxphotos/compare/v0.49.5...v0.49.6)
|
||||
|
||||
> 22 May 2022
|
||||
|
||||
- Bug fix [`7a52d41`](https://github.com/RhetTbull/osxphotos/commit/7a52d413a3cb130a6d11413775fad88933249f36)
|
||||
- Fixed detected_text for videos [`ab8b7b4`](https://github.com/RhetTbull/osxphotos/commit/ab8b7b4b198c00569b3852b9a21b0b032fad1940)
|
||||
|
||||
#### [v0.49.5](https://github.com/RhetTbull/osxphotos/compare/v0.49.4...v0.49.5)
|
||||
|
||||
> 22 May 2022
|
||||
|
||||
- Feature inspect command [`#701`](https://github.com/RhetTbull/osxphotos/pull/701)
|
||||
- Initial implementation of inspect command [`#700`](https://github.com/RhetTbull/osxphotos/pull/700)
|
||||
- Added inspect command [`128e84c`](https://github.com/RhetTbull/osxphotos/commit/128e84c7a4b9745d70e71e3bb1c48c9952431dec)
|
||||
|
||||
#### [v0.49.4](https://github.com/RhetTbull/osxphotos/compare/v0.49.3...v0.49.4)
|
||||
|
||||
> 21 May 2022
|
||||
|
||||
- Added timestamp to export_data in exportdb, #697 [`6400204`](https://github.com/RhetTbull/osxphotos/commit/64002044d2cbbd6fb3c2f0ab69ff6cd5ee2e8e25)
|
||||
- Added warning on hardlinks to exiftool command [`4e40d4b`](https://github.com/RhetTbull/osxphotos/commit/4e40d4b74e9b244b8eee602f839e595af4f99dfb)
|
||||
|
||||
#### [v0.49.3](https://github.com/RhetTbull/osxphotos/compare/v0.49.2...v0.49.3)
|
||||
|
||||
> 21 May 2022
|
||||
|
||||
- Updated docs [`0a7575b`](https://github.com/RhetTbull/osxphotos/commit/0a7575b889949f9e74ad716bc316e87f2599d4ad)
|
||||
- Added --uuid-info, --uuid-files to exportdb [`c776f30`](https://github.com/RhetTbull/osxphotos/commit/c776f3070d40ed960eabe3c21c57c354108ff5e4)
|
||||
|
||||
#### [v0.49.2](https://github.com/RhetTbull/osxphotos/compare/v0.49.1...v0.49.2)
|
||||
|
||||
> 21 May 2022
|
||||
|
||||
- Initial implementation of exiftool command, #691 [`#696`](https://github.com/RhetTbull/osxphotos/pull/696)
|
||||
- Added exiftool command [`8e9f279`](https://github.com/RhetTbull/osxphotos/commit/8e9f27995b56489da8968e77018a8a3d1bbe76bd)
|
||||
- Added example [skip ci] [`6d5af5c`](https://github.com/RhetTbull/osxphotos/commit/6d5af5c5e87aa0699da7291376cbf05c4237f6ec)
|
||||
- Updated docs [skip ci] [`3473c2e`](https://github.com/RhetTbull/osxphotos/commit/3473c2ece2b6eea9885893c85aa9eceb16921b94)
|
||||
- Updated test [`dfcb99f`](https://github.com/RhetTbull/osxphotos/commit/dfcb99f3774cb519c30a9fcf403ca9fdc3fed993)
|
||||
|
||||
#### [v0.49.1](https://github.com/RhetTbull/osxphotos/compare/v0.49.0...v0.49.1)
|
||||
|
||||
> 17 May 2022
|
||||
|
||||
@@ -107,6 +107,7 @@ Alternatively, you can also run the command line utility like this: ``python3 -m
|
||||
exportdb Utilities for working with the osxphotos export database
|
||||
help Print help; for help on commands: help <command>.
|
||||
info Print out descriptive info of the Photos library database.
|
||||
inspect Interactively inspect photos selected in Photos.
|
||||
install Install Python packages into the same environment as osxphotos
|
||||
keywords Print out keywords found in the Photos library.
|
||||
labels Print out image classification labels found in the Photos...
|
||||
|
||||
1
build.sh
@@ -35,6 +35,7 @@ echo "Building docs"
|
||||
|
||||
# copy docs to osxphotos/docs/docs.zip for use with `osxphotos docs` command
|
||||
echo "Zipping docs to osxphotos/docs/docs.zip"
|
||||
rm osxphotos/docs/docs.zip
|
||||
zip -r osxphotos/docs/docs.zip docs/*
|
||||
|
||||
# build the package
|
||||
|
||||
@@ -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: 3a7efae54b6e07cdaf59f7d2e9729b6e
|
||||
config: ad1b9f346384c93c414bf29025529641
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../genindex.html" /><link rel="search" title="Search" href="../search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>Overview: module code - osxphotos 0.49.2 documentation</title>
|
||||
<title>Overview: module code - osxphotos 0.50.4 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.49.2 documentation</div></a>
|
||||
<a href="../index.html"><div class="brand">osxphotos 0.50.4 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.49.2 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.4 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="../search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>osxphotos._constants - osxphotos 0.48.1 documentation</title>
|
||||
<title>osxphotos._constants - osxphotos 0.50.4 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.50.4 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.50.4 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">
|
||||
@@ -240,10 +240,8 @@
|
||||
<span class="c1"># Ranges for model version by Photos version</span>
|
||||
<span class="n">_PHOTOS_5_MODEL_VERSION</span> <span class="o">=</span> <span class="p">[</span><span class="mi">13000</span><span class="p">,</span> <span class="mi">13999</span><span class="p">]</span>
|
||||
<span class="n">_PHOTOS_6_MODEL_VERSION</span> <span class="o">=</span> <span class="p">[</span><span class="mi">14000</span><span class="p">,</span> <span class="mi">14999</span><span class="p">]</span>
|
||||
<span class="n">_PHOTOS_7_MODEL_VERSION</span> <span class="o">=</span> <span class="p">[</span>
|
||||
<span class="mi">15000</span><span class="p">,</span>
|
||||
<span class="mi">15999</span><span class="p">,</span>
|
||||
<span class="p">]</span> <span class="c1"># Monterey developer preview is 15134, 12.1 is 15331</span>
|
||||
<span class="n">_PHOTOS_7_MODEL_VERSION</span> <span class="o">=</span> <span class="p">[</span><span class="mi">15000</span><span class="p">,</span> <span class="mi">15999</span><span class="p">]</span> <span class="c1"># Dev preview: 15134, 12.1: 15331</span>
|
||||
<span class="n">_PHOTOS_8_MODEL_VERSION</span> <span class="o">=</span> <span class="p">[</span><span class="mi">16000</span><span class="p">,</span> <span class="mi">16999</span><span class="p">]</span> <span class="c1"># Ventura dev preview: 16119</span>
|
||||
|
||||
<span class="c1"># some table names differ between Photos 5 and Photos 6</span>
|
||||
<span class="n">_DB_TABLE_NAMES</span> <span class="o">=</span> <span class="p">{</span>
|
||||
@@ -283,6 +281,18 @@
|
||||
<span class="s2">"ASSET_ALBUM_TABLE"</span><span class="p">:</span> <span class="s2">"Z_27ASSETS"</span><span class="p">,</span>
|
||||
<span class="s2">"HDR_TYPE"</span><span class="p">:</span> <span class="s2">"ZHDRTYPE"</span><span class="p">,</span>
|
||||
<span class="p">},</span>
|
||||
<span class="mi">8</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="s2">"ASSET"</span><span class="p">:</span> <span class="s2">"ZASSET"</span><span class="p">,</span>
|
||||
<span class="s2">"KEYWORD_JOIN"</span><span class="p">:</span> <span class="s2">"Z_1KEYWORDS.Z_40KEYWORDS"</span><span class="p">,</span>
|
||||
<span class="s2">"ALBUM_JOIN"</span><span class="p">:</span> <span class="s2">"Z_28ASSETS.Z_3ASSETS"</span><span class="p">,</span>
|
||||
<span class="s2">"ALBUM_SORT_ORDER"</span><span class="p">:</span> <span class="s2">"Z_28ASSETS.Z_FOK_3ASSETS"</span><span class="p">,</span>
|
||||
<span class="s2">"IMPORT_FOK"</span><span class="p">:</span> <span class="s2">"null"</span><span class="p">,</span>
|
||||
<span class="s2">"DEPTH_STATE"</span><span class="p">:</span> <span class="s2">"ZASSET.ZDEPTHTYPE"</span><span class="p">,</span>
|
||||
<span class="s2">"UTI_ORIGINAL"</span><span class="p">:</span> <span class="s2">"ZINTERNALRESOURCE.ZCOMPACTUTI"</span><span class="p">,</span>
|
||||
<span class="s2">"ASSET_ALBUM_JOIN"</span><span class="p">:</span> <span class="s2">"Z_28ASSETS.Z_28ALBUMS"</span><span class="p">,</span>
|
||||
<span class="s2">"ASSET_ALBUM_TABLE"</span><span class="p">:</span> <span class="s2">"Z_28ASSETS"</span><span class="p">,</span>
|
||||
<span class="s2">"HDR_TYPE"</span><span class="p">:</span> <span class="s2">"ZHDRTYPE"</span><span class="p">,</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="c1"># which version operating systems have been tested</span>
|
||||
@@ -303,6 +313,7 @@
|
||||
<span class="p">(</span><span class="s2">"12"</span><span class="p">,</span> <span class="s2">"1"</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s2">"12"</span><span class="p">,</span> <span class="s2">"2"</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s2">"12"</span><span class="p">,</span> <span class="s2">"3"</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s2">"12"</span><span class="p">,</span> <span class="s2">"4"</span><span class="p">),</span>
|
||||
<span class="p">]</span>
|
||||
|
||||
<span class="c1"># Photos 5 has persons who are empty string if unidentified face</span>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>osxphotos.export_db - osxphotos 0.49.2 documentation</title>
|
||||
<title>osxphotos.export_db - osxphotos 0.50.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.49.2 documentation</div></a>
|
||||
<a href="../../index.html"><div class="brand">osxphotos 0.50.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.49.2 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.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">
|
||||
@@ -202,8 +202,10 @@
|
||||
<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">os.path</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">re</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>
|
||||
@@ -211,9 +213,9 @@
|
||||
<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">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">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">List</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>
|
||||
<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">retry_if_not_exception_type</span><span class="p">,</span> <span class="n">stop_after_attempt</span>
|
||||
|
||||
<span class="kn">from</span> <span class="nn">._constants</span> <span class="kn">import</span> <span class="n">OSXPHOTOS_EXPORT_DB</span>
|
||||
<span class="kn">from</span> <span class="nn">._version</span> <span class="kn">import</span> <span class="n">__version__</span>
|
||||
@@ -226,11 +228,11 @@
|
||||
<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">"7.0"</span>
|
||||
<span class="n">OSXPHOTOS_EXPORTDB_VERSION</span> <span class="o">=</span> <span class="s2">"7.1"</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="n">MAX_RETRY_ATTEMPTS</span> <span class="o">=</span> <span class="mi">3</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>
|
||||
@@ -295,7 +297,8 @@
|
||||
<span class="sd">"""returns path to export directory"""</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_path</span>
|
||||
|
||||
<div class="viewcode-block" id="ExportDB.get_file_record"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_file_record">[docs]</a> <span class="k">def</span> <span class="nf">get_file_record</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="n">Union</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="nb">str</span><span class="p">])</span> <span class="o">-></span> <span class="s2">"ExportRecord"</span><span class="p">:</span>
|
||||
<div class="viewcode-block" id="ExportDB.get_file_record"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_file_record">[docs]</a> <span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<span class="k">def</span> <span class="nf">get_file_record</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="n">Union</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="nb">str</span><span class="p">])</span> <span class="o">-></span> <span class="s2">"ExportRecord"</span><span class="p">:</span>
|
||||
<span class="sd">"""get info for filename and uuid</span>
|
||||
|
||||
<span class="sd"> Returns: an ExportRecord object or None if filename not found</span>
|
||||
@@ -312,7 +315,11 @@
|
||||
<span class="k">return</span> <span class="n">ExportRecord</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">filename_normalized</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="kc">None</span></div>
|
||||
|
||||
<div class="viewcode-block" id="ExportDB.create_file_record"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.create_file_record">[docs]</a> <span class="k">def</span> <span class="nf">create_file_record</span><span class="p">(</span>
|
||||
<div class="viewcode-block" id="ExportDB.create_file_record"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.create_file_record">[docs]</a> <span class="nd">@retry</span><span class="p">(</span>
|
||||
<span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">),</span>
|
||||
<span class="n">retry</span><span class="o">=</span><span class="n">retry_if_not_exception_type</span><span class="p">(</span><span class="n">sqlite3</span><span class="o">.</span><span class="n">IntegrityError</span><span class="p">),</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">def</span> <span class="nf">create_file_record</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="n">Union</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="nb">str</span><span class="p">],</span> <span class="n">uuid</span><span class="p">:</span> <span class="nb">str</span>
|
||||
<span class="p">)</span> <span class="o">-></span> <span class="s2">"ExportRecord"</span><span class="p">:</span>
|
||||
<span class="sd">"""create a new record for filename and uuid</span>
|
||||
@@ -330,7 +337,11 @@
|
||||
<span class="n">conn</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">ExportRecord</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">filename_normalized</span><span class="p">)</span></div>
|
||||
|
||||
<div class="viewcode-block" id="ExportDB.create_or_get_file_record"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.create_or_get_file_record">[docs]</a> <span class="k">def</span> <span class="nf">create_or_get_file_record</span><span class="p">(</span>
|
||||
<div class="viewcode-block" id="ExportDB.create_or_get_file_record"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.create_or_get_file_record">[docs]</a> <span class="nd">@retry</span><span class="p">(</span>
|
||||
<span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">),</span>
|
||||
<span class="n">retry</span><span class="o">=</span><span class="n">retry_if_not_exception_type</span><span class="p">(</span><span class="n">sqlite3</span><span class="o">.</span><span class="n">IntegrityError</span><span class="p">),</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">def</span> <span class="nf">create_or_get_file_record</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="n">Union</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="nb">str</span><span class="p">],</span> <span class="n">uuid</span><span class="p">:</span> <span class="nb">str</span>
|
||||
<span class="p">)</span> <span class="o">-></span> <span class="s2">"ExportRecord"</span><span class="p">:</span>
|
||||
<span class="sd">"""create a new record for filename and uuid or return existing record</span>
|
||||
@@ -348,102 +359,138 @@
|
||||
<span class="n">conn</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">ExportRecord</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">filename_normalized</span><span class="p">)</span></div>
|
||||
|
||||
<div class="viewcode-block" id="ExportDB.get_uuid_for_file"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_uuid_for_file">[docs]</a> <span class="k">def</span> <span class="nf">get_uuid_for_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">):</span>
|
||||
<div class="viewcode-block" id="ExportDB.get_uuid_for_file"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_uuid_for_file">[docs]</a> <span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<span class="k">def</span> <span class="nf">get_uuid_for_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">):</span>
|
||||
<span class="sd">"""query database for filename and return UUID</span>
|
||||
<span class="sd"> returns None if filename not found in database</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="n">filepath_normalized</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_normalize_filepath_relative</span><span class="p">(</span><span class="n">filename</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">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="s2">"SELECT uuid FROM export_data WHERE filepath_normalized = ?"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">filepath_normalized</span><span class="p">,),</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">results</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
<span class="n">uuid</span> <span class="o">=</span> <span class="n">results</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">if</span> <span class="n">results</span> <span class="k">else</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">uuid</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="k">return</span> <span class="n">uuid</span></div>
|
||||
<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="s2">"SELECT uuid FROM export_data WHERE filepath_normalized = ?"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">filepath_normalized</span><span class="p">,),</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">results</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">results</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">if</span> <span class="n">results</span> <span class="k">else</span> <span class="kc">None</span></div>
|
||||
|
||||
<div class="viewcode-block" id="ExportDB.get_photoinfo_for_uuid"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_photoinfo_for_uuid">[docs]</a> <span class="k">def</span> <span class="nf">get_photoinfo_for_uuid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="p">):</span>
|
||||
<div class="viewcode-block" id="ExportDB.get_files_for_uuid"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_files_for_uuid">[docs]</a> <span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<span class="k">def</span> <span class="nf">get_files_for_uuid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="n">List</span><span class="p">:</span>
|
||||
<span class="sd">"""query database for UUID and return list of files associated with UUID or empty list"""</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="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="s2">"SELECT filepath FROM export_data WHERE uuid = ?"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">uuid</span><span class="p">,),</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">results</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">return</span> <span class="p">[</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">export_dir</span><span class="p">,</span> <span class="n">r</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">]</span></div>
|
||||
|
||||
<div class="viewcode-block" id="ExportDB.get_photoinfo_for_uuid"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_photoinfo_for_uuid">[docs]</a> <span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<span class="k">def</span> <span class="nf">get_photoinfo_for_uuid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="p">):</span>
|
||||
<span class="sd">"""returns the photoinfo JSON struct for a UUID"""</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="s2">"SELECT photoinfo FROM photoinfo WHERE uuid = ?"</span><span class="p">,</span> <span class="p">(</span><span class="n">uuid</span><span class="p">,))</span>
|
||||
<span class="n">results</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
<span class="n">info</span> <span class="o">=</span> <span class="n">results</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">if</span> <span class="n">results</span> <span class="k">else</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">info</span> <span class="o">=</span> <span class="kc">None</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="s2">"SELECT photoinfo FROM photoinfo WHERE uuid = ?"</span><span class="p">,</span> <span class="p">(</span><span class="n">uuid</span><span class="p">,))</span>
|
||||
<span class="n">results</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">results</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">if</span> <span class="n">results</span> <span class="k">else</span> <span class="kc">None</span></div>
|
||||
|
||||
<span class="k">return</span> <span class="n">info</span></div>
|
||||
|
||||
<div class="viewcode-block" id="ExportDB.set_photoinfo_for_uuid"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.set_photoinfo_for_uuid">[docs]</a> <span class="k">def</span> <span class="nf">set_photoinfo_for_uuid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="p">,</span> <span class="n">info</span><span class="p">):</span>
|
||||
<div class="viewcode-block" id="ExportDB.set_photoinfo_for_uuid"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.set_photoinfo_for_uuid">[docs]</a> <span class="nd">@retry</span><span class="p">(</span>
|
||||
<span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">),</span>
|
||||
<span class="n">retry</span><span class="o">=</span><span class="n">retry_if_not_exception_type</span><span class="p">(</span><span class="n">sqlite3</span><span class="o">.</span><span class="n">IntegrityError</span><span class="p">),</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">def</span> <span class="nf">set_photoinfo_for_uuid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="p">,</span> <span class="n">info</span><span class="p">):</span>
|
||||
<span class="sd">"""sets the photoinfo JSON struct for a UUID"""</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="s2">"INSERT OR REPLACE INTO photoinfo(uuid, photoinfo) VALUES (?, ?);"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">uuid</span><span class="p">,</span> <span class="n">info</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>
|
||||
<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="s2">"INSERT OR REPLACE INTO photoinfo(uuid, photoinfo) VALUES (?, ?);"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">uuid</span><span class="p">,</span> <span class="n">info</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></div>
|
||||
|
||||
<div class="viewcode-block" id="ExportDB.get_previous_uuids"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_previous_uuids">[docs]</a> <span class="k">def</span> <span class="nf">get_previous_uuids</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<div class="viewcode-block" id="ExportDB.get_target_for_file"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_target_for_file">[docs]</a> <span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<span class="k">def</span> <span class="nf">get_target_for_file</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">str</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="p">)</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="sd">"""query database for file matching file name and return the matching filename if there is one;</span>
|
||||
<span class="sd"> otherwise return None; looks for file.ext, file (1).ext, file (2).ext and so on to find the</span>
|
||||
<span class="sd"> actual target name that was used to export filename</span>
|
||||
|
||||
<span class="sd"> Returns: the matching filename or None if no match found</span>
|
||||
<span class="sd"> """</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="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">filepath_normalized</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_normalize_filepath_relative</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
|
||||
<span class="n">filepath_stem</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">filepath_normalized</span><span class="p">)[</span><span class="mi">0</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="s2">"SELECT uuid, filepath, filepath_normalized FROM export_data WHERE uuid = ? AND filepath_normalized LIKE ?"</span><span class="p">,</span>
|
||||
<span class="p">(</span>
|
||||
<span class="n">uuid</span><span class="p">,</span>
|
||||
<span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">filepath_stem</span><span class="si">}</span><span class="s2">%"</span><span class="p">,</span>
|
||||
<span class="p">),</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">results</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">for</span> <span class="n">result</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
|
||||
<span class="n">filepath_normalized</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="mi">2</span><span class="p">])[</span><span class="mi">0</span><span class="p">]</span>
|
||||
<span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">match</span><span class="p">(</span>
|
||||
<span class="n">re</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">filepath_stem</span><span class="p">)</span> <span class="o">+</span> <span class="sa">r</span><span class="s2">"(\s\(\d+\))?$"</span><span class="p">,</span> <span class="n">filepath_normalized</span>
|
||||
<span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">export_dir</span><span class="p">,</span> <span class="n">result</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
|
||||
|
||||
<span class="k">return</span> <span class="kc">None</span></div>
|
||||
|
||||
<div class="viewcode-block" id="ExportDB.get_previous_uuids"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_previous_uuids">[docs]</a> <span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<span class="k">def</span> <span class="nf">get_previous_uuids</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""returns list of UUIDs of previously exported photos found in export database"""</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="n">previous_uuids</span> <span class="o">=</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="s2">"SELECT DISTINCT uuid FROM export_data"</span><span class="p">)</span>
|
||||
<span class="n">results</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="n">previous_uuids</span> <span class="o">=</span> <span class="p">[</span><span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">results</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">return</span> <span class="n">previous_uuids</span></div>
|
||||
<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="s2">"SELECT DISTINCT uuid FROM export_data"</span><span class="p">)</span>
|
||||
<span class="n">results</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">return</span> <span class="p">[</span><span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">results</span><span class="p">]</span></div>
|
||||
|
||||
<div class="viewcode-block" id="ExportDB.set_config"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.set_config">[docs]</a> <span class="k">def</span> <span class="nf">set_config</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_data</span><span class="p">):</span>
|
||||
<div class="viewcode-block" id="ExportDB.set_config"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.set_config">[docs]</a> <span class="nd">@retry</span><span class="p">(</span>
|
||||
<span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">),</span>
|
||||
<span class="n">retry</span><span class="o">=</span><span class="n">retry_if_not_exception_type</span><span class="p">(</span><span class="n">sqlite3</span><span class="o">.</span><span class="n">IntegrityError</span><span class="p">),</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">def</span> <span class="nf">set_config</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_data</span><span class="p">):</span>
|
||||
<span class="sd">"""set config in the database"""</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="s2">"INSERT OR REPLACE INTO config(datetime, config) VALUES (?, ?);"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">dt</span><span class="p">,</span> <span class="n">config_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>
|
||||
<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="s2">"INSERT OR REPLACE INTO config(datetime, config) VALUES (?, ?);"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">dt</span><span class="p">,</span> <span class="n">config_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></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>
|
||||
<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="nd">@retry</span><span class="p">(</span>
|
||||
<span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">),</span>
|
||||
<span class="n">retry</span><span class="o">=</span><span class="n">retry_if_not_exception_type</span><span class="p">(</span><span class="n">sqlite3</span><span class="o">.</span><span class="n">IntegrityError</span><span class="p">),</span>
|
||||
<span class="p">)</span>
|
||||
<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>
|
||||
<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></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>
|
||||
<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="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<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>
|
||||
@@ -458,47 +505,67 @@
|
||||
<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="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">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">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">return</span> <span class="n">results</span></div>
|
||||
|
||||
<div class="viewcode-block" id="ExportDB.get_exported_files"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_exported_files">[docs]</a> <span class="k">def</span> <span class="nf">get_exported_files</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<div class="viewcode-block" id="ExportDB.get_exported_files"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_exported_files">[docs]</a> <span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<span class="k">def</span> <span class="nf">get_exported_files</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Returns tuple of (uuid, filepath) for all paths of all exported files tracked in the database"""</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="s2">"SELECT uuid, filepath FROM export_data"</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">return</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="s2">"SELECT uuid, filepath FROM export_data"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">while</span> <span class="n">row</span> <span class="o">:=</span> <span class="n">c</span><span class="o">.</span><span class="n">fetchone</span><span class="p">():</span>
|
||||
<span class="k">yield</span> <span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">export_dir</span><span class="p">,</span> <span class="n">row</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
|
||||
<span class="k">return</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>
|
||||
<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">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.delete_data_for_uuid"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.delete_data_for_uuid">[docs]</a> <span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<span class="k">def</span> <span class="nf">delete_data_for_uuid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="p">):</span>
|
||||
<span class="sd">"""Delete all exportdb data for given UUID"""</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="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">count</span> <span class="o">=</span> <span class="mi">0</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"DELETE FROM export_data WHERE uuid = ?;"</span><span class="p">,</span> <span class="p">(</span><span class="n">uuid</span><span class="p">,))</span>
|
||||
<span class="n">count</span> <span class="o">+=</span> <span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"SELECT CHANGES();"</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()[</span><span class="mi">0</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="s2">"DELETE FROM photoinfo WHERE uuid = ?;"</span><span class="p">,</span> <span class="p">(</span><span class="n">uuid</span><span class="p">,))</span>
|
||||
<span class="n">count</span> <span class="o">+=</span> <span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"SELECT CHANGES();"</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()[</span><span class="mi">0</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">return</span> <span class="n">count</span></div>
|
||||
|
||||
<div class="viewcode-block" id="ExportDB.delete_data_for_filepath"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.delete_data_for_filepath">[docs]</a> <span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<span class="k">def</span> <span class="nf">delete_data_for_filepath</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="sd">"""Delete all exportdb data for given filepath"""</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="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">filepath_normalized</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_normalize_filepath_relative</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span>
|
||||
<span class="n">results</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s2">"SELECT uuid FROM export_data WHERE filepath_normalized = ?;"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">filepath_normalized</span><span class="p">,),</span>
|
||||
<span class="p">)</span><span class="o">.</span><span class="n">fetchall</span><span class="p">()</span>
|
||||
<span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
|
||||
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
|
||||
<span class="n">count</span> <span class="o">+=</span> <span class="bp">self</span><span class="o">.</span><span class="n">delete_data_for_uuid</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
|
||||
<span class="k">return</span> <span class="n">count</span></div>
|
||||
|
||||
<div class="viewcode-block" id="ExportDB.close"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.close">[docs]</a> <span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<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="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></div>
|
||||
|
||||
<span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</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="sd">"""open export database and return a db connection</span>
|
||||
<span class="sd"> if dbfile does not exist, will create and initialize the database</span>
|
||||
@@ -508,8 +575,6 @@
|
||||
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">dbfile</span><span class="p">):</span>
|
||||
<span class="n">conn</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_db_connection</span><span class="p">(</span><span class="n">dbfile</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">conn</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">Exception</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Error getting connection to database </span><span class="si">{</span><span class="n">dbfile</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_create_or_migrate_db_tables</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">was_created</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">was_upgraded</span> <span class="o">=</span> <span class="p">()</span>
|
||||
@@ -533,16 +598,12 @@
|
||||
|
||||
<span class="k">return</span> <span class="n">conn</span>
|
||||
|
||||
<span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<span class="k">def</span> <span class="nf">_get_db_connection</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dbfile</span><span class="p">):</span>
|
||||
<span class="sd">"""return db connection to dbname"""</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">conn</span> <span class="o">=</span> <span class="n">sqlite3</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">dbfile</span><span class="p">)</span>
|
||||
<span class="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">conn</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">conn</span>
|
||||
<span class="k">return</span> <span class="n">sqlite3</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">dbfile</span><span class="p">)</span>
|
||||
|
||||
<span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<span class="k">def</span> <span class="nf">_get_database_version</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">"""return tuple of (osxphotos, exportdb) versions for database connection conn"""</span>
|
||||
<span class="n">version_info</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
@@ -638,18 +699,15 @@
|
||||
<span class="sd">""" CREATE UNIQUE INDEX IF NOT EXISTS idx_detected_text on detected_text (uuid);"""</span><span class="p">,</span>
|
||||
<span class="p">]</span>
|
||||
<span class="c1"># create the tables if needed</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="k">for</span> <span class="n">cmd</span> <span class="ow">in</span> <span class="n">sql_commands</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="n">cmd</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="s2">"INSERT INTO version(osxphotos, exportdb) VALUES (?, ?);"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">__version__</span><span class="p">,</span> <span class="n">OSXPHOTOS_EXPORTDB_VERSION</span><span class="p">),</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="s2">"INSERT INTO about(about) VALUES (?);"</span><span class="p">,</span> <span class="p">(</span><span class="n">OSXPHOTOS_ABOUT_STRING</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="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="k">for</span> <span class="n">cmd</span> <span class="ow">in</span> <span class="n">sql_commands</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="n">cmd</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="s2">"INSERT INTO version(osxphotos, exportdb) VALUES (?, ?);"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">__version__</span><span class="p">,</span> <span class="n">OSXPHOTOS_EXPORTDB_VERSION</span><span class="p">),</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="s2">"INSERT INTO about(about) VALUES (?);"</span><span class="p">,</span> <span class="p">(</span><span class="n">OSXPHOTOS_ABOUT_STRING</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="c1"># perform needed migrations</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"><</span> <span class="s2">"4.3"</span><span class="p">:</span>
|
||||
@@ -666,6 +724,10 @@
|
||||
<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="k">if</span> <span class="n">version</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o"><</span> <span class="s2">"7.1"</span><span class="p">:</span>
|
||||
<span class="c1"># add timestamp to export_data</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_migrate_7_0_to_7_1</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>
|
||||
|
||||
@@ -674,6 +736,7 @@
|
||||
<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="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</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>
|
||||
<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="n">datetime</span><span class="o">.</span><span class="n">timezone</span><span class="o">.</span><span class="n">utc</span><span class="p">)</span><span class="o">.</span><span class="n">isoformat</span><span class="p">()</span>
|
||||
<span class="n">python_path</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">executable</span>
|
||||
@@ -681,16 +744,13 @@
|
||||
<span class="n">args</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="mi">1</span><span class="p">:])</span> <span class="k">if</span> <span class="nb">len</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="o">></span> <span class="mi">1</span> <span class="k">else</span> <span class="s2">""</span>
|
||||
<span class="n">cwd</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</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">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="s2">"INSERT INTO runs (datetime, python_path, script_name, args, cwd) VALUES (?, ?, ?, ?, ?)"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">dt</span><span class="p">,</span> <span class="n">python_path</span><span class="p">,</span> <span class="n">cmd</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">cwd</span><span class="p">),</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="s2">"INSERT INTO runs (datetime, python_path, script_name, args, cwd) VALUES (?, ?, ?, ?, ?)"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">dt</span><span class="p">,</span> <span class="n">python_path</span><span class="p">,</span> <span class="n">cmd</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">cwd</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>
|
||||
<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="nf">_relative_filepath</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">Union</span><span class="p">[</span><span class="nb">str</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="o">-></span> <span class="nb">str</span><span class="p">:</span>
|
||||
<span class="sd">"""return filepath relative to self._path"""</span>
|
||||
@@ -746,158 +806,169 @@
|
||||
|
||||
<span class="k">def</span> <span class="nf">_migrate_4_3_to_5_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="sd">"""Migrate database from version 4.3 to 5.0"""</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="c1"># add metadata column to files to support --force-update</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"ALTER TABLE files ADD COLUMN metadata TEXT;"</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="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="c1"># add metadata column to files to support --force-update</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"ALTER TABLE files ADD COLUMN metadata TEXT;"</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="nf">_migrate_5_0_to_6_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">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># add export_data table</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_data(</span>
|
||||
<span class="sd"> id INTEGER PRIMARY KEY,</span>
|
||||
<span class="sd"> filepath_normalized TEXT NOT NULL,</span>
|
||||
<span class="sd"> filepath TEXT NOT NULL,</span>
|
||||
<span class="sd"> uuid TEXT NOT NULL,</span>
|
||||
<span class="sd"> src_mode INTEGER,</span>
|
||||
<span class="sd"> src_size INTEGER,</span>
|
||||
<span class="sd"> src_mtime REAL,</span>
|
||||
<span class="sd"> dest_mode INTEGER,</span>
|
||||
<span class="sd"> dest_size INTEGER,</span>
|
||||
<span class="sd"> dest_mtime REAL,</span>
|
||||
<span class="sd"> digest TEXT,</span>
|
||||
<span class="sd"> exifdata JSON,</span>
|
||||
<span class="sd"> export_options INTEGER,</span>
|
||||
<span class="sd"> UNIQUE(filepath_normalized)</span>
|
||||
<span class="sd"> ); """</span><span class="p">,</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 UNIQUE INDEX IF NOT EXISTS idx_export_data_filepath_normalized on export_data (filepath_normalized); """</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="c1"># add export_data table</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_data(</span>
|
||||
<span class="sd"> id INTEGER PRIMARY KEY,</span>
|
||||
<span class="sd"> filepath_normalized TEXT NOT NULL,</span>
|
||||
<span class="sd"> filepath TEXT NOT NULL,</span>
|
||||
<span class="sd"> uuid TEXT NOT NULL,</span>
|
||||
<span class="sd"> src_mode INTEGER,</span>
|
||||
<span class="sd"> src_size INTEGER,</span>
|
||||
<span class="sd"> src_mtime REAL,</span>
|
||||
<span class="sd"> dest_mode INTEGER,</span>
|
||||
<span class="sd"> dest_size INTEGER,</span>
|
||||
<span class="sd"> dest_mtime REAL,</span>
|
||||
<span class="sd"> digest TEXT,</span>
|
||||
<span class="sd"> exifdata JSON,</span>
|
||||
<span class="sd"> export_options INTEGER,</span>
|
||||
<span class="sd"> UNIQUE(filepath_normalized)</span>
|
||||
<span class="sd"> ); """</span><span class="p">,</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 UNIQUE INDEX IF NOT EXISTS idx_export_data_filepath_normalized on export_data (filepath_normalized); """</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># migrate data</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_data (filepath_normalized, filepath, uuid) SELECT filepath_normalized, filepath, uuid FROM files;"""</span><span class="p">,</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">""" UPDATE export_data </span>
|
||||
<span class="sd"> SET (src_mode, src_size, src_mtime) = </span>
|
||||
<span class="sd"> (SELECT mode, size, mtime </span>
|
||||
<span class="sd"> FROM edited </span>
|
||||
<span class="sd"> WHERE export_data.filepath_normalized = edited.filepath_normalized);</span>
|
||||
<span class="sd"> """</span><span class="p">,</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">""" UPDATE export_data </span>
|
||||
<span class="sd"> SET (dest_mode, dest_size, dest_mtime) = </span>
|
||||
<span class="sd"> (SELECT orig_mode, orig_size, orig_mtime </span>
|
||||
<span class="sd"> FROM files </span>
|
||||
<span class="sd"> WHERE export_data.filepath_normalized = files.filepath_normalized);</span>
|
||||
<span class="sd"> """</span><span class="p">,</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">""" UPDATE export_data SET digest = </span>
|
||||
<span class="sd"> (SELECT metadata FROM files </span>
|
||||
<span class="sd"> WHERE files.filepath_normalized = export_data.filepath_normalized</span>
|
||||
<span class="sd"> ); """</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">""" UPDATE export_data SET exifdata = </span>
|
||||
<span class="sd"> (SELECT json_exifdata FROM exifdata </span>
|
||||
<span class="sd"> WHERE exifdata.filepath_normalized = export_data.filepath_normalized</span>
|
||||
<span class="sd"> ); """</span>
|
||||
<span class="p">)</span>
|
||||
<span class="c1"># migrate data</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_data (filepath_normalized, filepath, uuid) SELECT filepath_normalized, filepath, uuid FROM files;"""</span><span class="p">,</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">""" UPDATE export_data </span>
|
||||
<span class="sd"> SET (src_mode, src_size, src_mtime) = </span>
|
||||
<span class="sd"> (SELECT mode, size, mtime </span>
|
||||
<span class="sd"> FROM edited </span>
|
||||
<span class="sd"> WHERE export_data.filepath_normalized = edited.filepath_normalized);</span>
|
||||
<span class="sd"> """</span><span class="p">,</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">""" UPDATE export_data </span>
|
||||
<span class="sd"> SET (dest_mode, dest_size, dest_mtime) = </span>
|
||||
<span class="sd"> (SELECT orig_mode, orig_size, orig_mtime </span>
|
||||
<span class="sd"> FROM files </span>
|
||||
<span class="sd"> WHERE export_data.filepath_normalized = files.filepath_normalized);</span>
|
||||
<span class="sd"> """</span><span class="p">,</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">""" UPDATE export_data SET digest = </span>
|
||||
<span class="sd"> (SELECT metadata FROM files </span>
|
||||
<span class="sd"> WHERE files.filepath_normalized = export_data.filepath_normalized</span>
|
||||
<span class="sd"> ); """</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">""" UPDATE export_data SET exifdata = </span>
|
||||
<span class="sd"> (SELECT json_exifdata FROM exifdata </span>
|
||||
<span class="sd"> WHERE exifdata.filepath_normalized = export_data.filepath_normalized</span>
|
||||
<span class="sd"> ); """</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># create config table</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 config (</span>
|
||||
<span class="sd"> id INTEGER PRIMARY KEY,</span>
|
||||
<span class="sd"> datetime TEXT,</span>
|
||||
<span class="sd"> config TEXT </span>
|
||||
<span class="sd"> ); """</span>
|
||||
<span class="p">)</span>
|
||||
<span class="c1"># create config table</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 config (</span>
|
||||
<span class="sd"> id INTEGER PRIMARY KEY,</span>
|
||||
<span class="sd"> datetime TEXT,</span>
|
||||
<span class="sd"> config TEXT </span>
|
||||
<span class="sd"> ); """</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># create photoinfo table</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 photoinfo (</span>
|
||||
<span class="sd"> id INTEGER PRIMARY KEY,</span>
|
||||
<span class="sd"> uuid TEXT NOT NULL,</span>
|
||||
<span class="sd"> photoinfo JSON,</span>
|
||||
<span class="sd"> UNIQUE(uuid)</span>
|
||||
<span class="sd"> ); """</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 UNIQUE INDEX IF NOT EXISTS idx_photoinfo_uuid on photoinfo (uuid);"""</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 photoinfo (uuid, photoinfo) SELECT uuid, json_info FROM info;"""</span>
|
||||
<span class="p">)</span>
|
||||
<span class="c1"># create photoinfo table</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 photoinfo (</span>
|
||||
<span class="sd"> id INTEGER PRIMARY KEY,</span>
|
||||
<span class="sd"> uuid TEXT NOT NULL,</span>
|
||||
<span class="sd"> photoinfo JSON,</span>
|
||||
<span class="sd"> UNIQUE(uuid)</span>
|
||||
<span class="sd"> ); """</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 UNIQUE INDEX IF NOT EXISTS idx_photoinfo_uuid on photoinfo (uuid);"""</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 photoinfo (uuid, photoinfo) SELECT uuid, json_info FROM info;"""</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># drop indexes no longer needed</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"DROP INDEX IF EXISTS idx_files_filepath_normalized;"</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="s2">"DROP INDEX IF EXISTS idx_exifdata_filename;"</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="s2">"DROP INDEX IF EXISTS idx_edited_filename;"</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="s2">"DROP INDEX IF EXISTS idx_converted_filename;"</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="s2">"DROP INDEX IF EXISTS idx_sidecar_filename;"</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="s2">"DROP INDEX IF EXISTS idx_detected_text;"</span><span class="p">)</span>
|
||||
<span class="c1"># drop indexes no longer needed</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"DROP INDEX IF EXISTS idx_files_filepath_normalized;"</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="s2">"DROP INDEX IF EXISTS idx_exifdata_filename;"</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="s2">"DROP INDEX IF EXISTS idx_edited_filename;"</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="s2">"DROP INDEX IF EXISTS idx_converted_filename;"</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="s2">"DROP INDEX IF EXISTS idx_sidecar_filename;"</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="s2">"DROP INDEX IF EXISTS idx_detected_text;"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># drop tables no longer needed</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"DROP TABLE IF EXISTS files;"</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="s2">"DROP TABLE IF EXISTS info;"</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="s2">"DROP TABLE IF EXISTS exifdata;"</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="s2">"DROP TABLE IF EXISTS edited;"</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="s2">"DROP TABLE IF EXISTS converted;"</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="s2">"DROP TABLE IF EXISTS sidecar;"</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="s2">"DROP TABLE IF EXISTS detected_text;"</span><span class="p">)</span>
|
||||
<span class="c1"># drop tables no longer needed</span>
|
||||
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"DROP TABLE IF EXISTS files;"</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="s2">"DROP TABLE IF EXISTS info;"</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="s2">"DROP TABLE IF EXISTS exifdata;"</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="s2">"DROP TABLE IF EXISTS edited;"</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="s2">"DROP TABLE IF EXISTS converted;"</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="s2">"DROP TABLE IF EXISTS sidecar;"</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="s2">"DROP TABLE IF EXISTS detected_text;"</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="n">conn</span><span class="o">.</span><span class="n">commit</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">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">"""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="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"># 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="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">def</span> <span class="nf">_migrate_7_0_to_7_1</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="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="s2">"""ALTER TABLE export_data ADD COLUMN timestamp DATETIME;"""</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"> CREATE TRIGGER insert_timestamp_trigger</span>
|
||||
<span class="sd"> AFTER INSERT ON export_data</span>
|
||||
<span class="sd"> BEGIN</span>
|
||||
<span class="sd"> UPDATE export_data SET timestamp = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE id = NEW.id;</span>
|
||||
<span class="sd"> END;</span>
|
||||
<span class="sd"> """</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"> CREATE TRIGGER update_timestamp_trigger</span>
|
||||
<span class="sd"> AFTER UPDATE On export_data</span>
|
||||
<span class="sd"> BEGIN</span>
|
||||
<span class="sd"> UPDATE export_data SET timestamp = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE id = NEW.id;</span>
|
||||
<span class="sd"> END;</span>
|
||||
<span class="sd"> """</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="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>
|
||||
<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">"""DELETE FROM config</span>
|
||||
<span class="sd"> WHERE id < (</span>
|
||||
<span class="sd"> SELECT MIN(id)</span>
|
||||
<span class="sd"> FROM (SELECT id FROM config ORDER BY id DESC LIMIT 9)</span>
|
||||
<span class="sd"> );</span>
|
||||
<span class="sd"> """</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>
|
||||
<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">"""DELETE FROM config</span>
|
||||
<span class="sd"> WHERE id < (</span>
|
||||
<span class="sd"> SELECT MIN(id)</span>
|
||||
<span class="sd"> FROM (SELECT id FROM config ORDER BY id DESC LIMIT 9)</span>
|
||||
<span class="sd"> );</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">conn</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span></div>
|
||||
|
||||
|
||||
<span class="k">class</span> <span class="nc">ExportDBInMemory</span><span class="p">(</span><span class="n">ExportDB</span><span class="p">):</span>
|
||||
@@ -949,14 +1020,13 @@
|
||||
<span class="n">conn_on_disk</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
|
||||
<span class="n">conn_on_disk</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||||
|
||||
<span class="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</span><span class="p">))</span>
|
||||
<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>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_conn</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">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">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_conn</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="nd">@retry</span><span class="p">(</span><span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="n">MAX_RETRY_ATTEMPTS</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>
|
||||
@@ -990,13 +1060,7 @@
|
||||
|
||||
<span class="k">def</span> <span class="nf">_get_db_connection</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""return db connection to in memory database"""</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">conn</span> <span class="o">=</span> <span class="n">sqlite3</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s2">":memory:"</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="n">conn</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">conn</span>
|
||||
<span class="k">return</span> <span class="n">sqlite3</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s2">":memory:"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">_dump_db</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="n">sqlite3</span><span class="o">.</span><span class="n">Connection</span><span class="p">)</span> <span class="o">-></span> <span class="n">StringIO</span><span class="p">:</span>
|
||||
<span class="sd">"""dump sqlite db to a string buffer"""</span>
|
||||
@@ -1012,7 +1076,7 @@
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||||
|
||||
|
||||
<span class="k">class</span> <span class="nc">ExportDBTemp</span><span class="p">(</span><span class="n">ExportDBInMemory</span><span class="p">):</span>
|
||||
<div class="viewcode-block" id="ExportDBTemp"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDBTemp">[docs]</a><span class="k">class</span> <span class="nc">ExportDBTemp</span><span class="p">(</span><span class="n">ExportDBInMemory</span><span class="p">):</span>
|
||||
<span class="sd">"""Temporary in-memory version of ExportDB"""</span>
|
||||
|
||||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
@@ -1027,7 +1091,7 @@
|
||||
<span class="n">filepath</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">filepath</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="k">return</span> <span class="n">filepath</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
|
||||
<span class="k">return</span> <span class="n">filepath</span>
|
||||
<span class="k">return</span> <span class="n">filepath</span></div>
|
||||
|
||||
|
||||
<span class="k">class</span> <span class="nc">ExportRecord</span><span class="p">:</span>
|
||||
@@ -1244,6 +1308,21 @@
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_context_manager</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="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">timestamp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""returns the timestamp value"""</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="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="k">if</span> <span class="n">row</span> <span class="o">:=</span> <span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s2">"SELECT timestamp FROM export_data WHERE filepath_normalized = ?;"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_filepath_normalized</span><span class="p">,),</span>
|
||||
<span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">():</span>
|
||||
<span class="k">return</span> <span class="n">row</span><span class="p">[</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="sa">f</span><span class="s2">"No timestamp found in database for </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_filepath_normalized</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Return dict of self"""</span>
|
||||
<span class="n">exifdata</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="bp">self</span><span class="o">.</span><span class="n">exifdata</span><span class="p">)</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">exifdata</span> <span class="k">else</span> <span class="kc">None</span>
|
||||
@@ -1252,6 +1331,7 @@
|
||||
<span class="s2">"filepath"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">filepath</span><span class="p">,</span>
|
||||
<span class="s2">"filepath_normalized"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">filepath_normalized</span><span class="p">,</span>
|
||||
<span class="s2">"uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
|
||||
<span class="s2">"timestamp"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span><span class="p">,</span>
|
||||
<span class="s2">"digest"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">digest</span><span class="p">,</span>
|
||||
<span class="s2">"src_sig"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">src_sig</span><span class="p">,</span>
|
||||
<span class="s2">"dest_sig"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">dest_sig</span><span class="p">,</span>
|
||||
|
||||
@@ -1,37 +1,200 @@
|
||||
<!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.personinfo - osxphotos 0.50.4 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.personinfo — 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.50.4 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.50.4 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.personinfo</h1><div class="highlight"><pre>
|
||||
<span></span><span class="sd">""" PhotoInfo and FaceInfo classes to expose info about persons and faces in the Photos library """</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.personinfo</h1><div class="highlight"><pre>
|
||||
<span></span><span class="sd">""" PhotoInfo and FaceInfo classes to expose info about persons and faces in the Photos library """</span>
|
||||
|
||||
<span class="kn">import</span> <span class="nn">json</span>
|
||||
<span class="kn">import</span> <span class="nn">logging</span>
|
||||
@@ -39,17 +202,17 @@
|
||||
|
||||
<span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">namedtuple</span>
|
||||
|
||||
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"PersonInfo"</span><span class="p">,</span> <span class="s2">"FaceInfo"</span><span class="p">,</span> <span class="s2">"rotate_image_point"</span><span class="p">]</span>
|
||||
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"PersonInfo"</span><span class="p">,</span> <span class="s2">"FaceInfo"</span><span class="p">,</span> <span class="s2">"rotate_image_point"</span><span class="p">]</span>
|
||||
|
||||
<span class="n">MWG_RS_Area</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s2">"MWG_RS_Area"</span><span class="p">,</span> <span class="p">[</span><span class="s2">"x"</span><span class="p">,</span> <span class="s2">"y"</span><span class="p">,</span> <span class="s2">"h"</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">])</span>
|
||||
<span class="n">MPRI_Reg_Rect</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s2">"MPRI_Reg_Rect"</span><span class="p">,</span> <span class="p">[</span><span class="s2">"x"</span><span class="p">,</span> <span class="s2">"y"</span><span class="p">,</span> <span class="s2">"h"</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">])</span>
|
||||
<span class="n">MWG_RS_Area</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s2">"MWG_RS_Area"</span><span class="p">,</span> <span class="p">[</span><span class="s2">"x"</span><span class="p">,</span> <span class="s2">"y"</span><span class="p">,</span> <span class="s2">"h"</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">])</span>
|
||||
<span class="n">MPRI_Reg_Rect</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s2">"MPRI_Reg_Rect"</span><span class="p">,</span> <span class="p">[</span><span class="s2">"x"</span><span class="p">,</span> <span class="s2">"y"</span><span class="p">,</span> <span class="s2">"h"</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">])</span>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="PersonInfo"><a class="viewcode-back" href="../../reference.html#osxphotos.PersonInfo">[docs]</a><span class="k">class</span> <span class="nc">PersonInfo</span><span class="p">:</span>
|
||||
<span class="sd">"""Info about a person in the Photos library"""</span>
|
||||
<span class="sd">"""Info about a person in the Photos library"""</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">db</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">pk</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="sd">"""Creates a new PersonInfo instance</span>
|
||||
<span class="sd">"""Creates a new PersonInfo instance</span>
|
||||
|
||||
<span class="sd"> Arguments:</span>
|
||||
<span class="sd"> db: instance of PhotosDB object</span>
|
||||
@@ -57,16 +220,16 @@
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> PersonInfo instance</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_db</span> <span class="o">=</span> <span class="n">db</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_pk</span> <span class="o">=</span> <span class="n">pk</span>
|
||||
|
||||
<span class="n">person</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_dbpersons_pk</span><span class="p">[</span><span class="n">pk</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">uuid</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"uuid"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"fullname"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">display_name</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"displayname"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">keyface</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"keyface"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">facecount</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"facecount"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">uuid</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"uuid"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"fullname"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">display_name</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"displayname"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">keyface</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"keyface"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">facecount</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"facecount"</span><span class="p">]</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">keyphoto</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
@@ -74,9 +237,9 @@
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_keyphoto</span>
|
||||
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
|
||||
<span class="n">person</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_dbpersons_pk</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="p">]</span>
|
||||
<span class="k">if</span> <span class="n">person</span><span class="p">[</span><span class="s2">"photo_uuid"</span><span class="p">]:</span>
|
||||
<span class="k">if</span> <span class="n">person</span><span class="p">[</span><span class="s2">"photo_uuid"</span><span class="p">]:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">key_photo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">get_photo</span><span class="p">(</span><span class="n">person</span><span class="p">[</span><span class="s2">"photo_uuid"</span><span class="p">])</span>
|
||||
<span class="n">key_photo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">get_photo</span><span class="p">(</span><span class="n">person</span><span class="p">[</span><span class="s2">"photo_uuid"</span><span class="p">])</span>
|
||||
<span class="k">except</span> <span class="ne">IndexError</span><span class="p">:</span>
|
||||
<span class="n">key_photo</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
@@ -86,14 +249,14 @@
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">photos</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Returns list of PhotoInfo objects associated with this person"""</span>
|
||||
<span class="sd">"""Returns list of PhotoInfo objects associated with this person"""</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">photos_by_uuid</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">_dbfaces_pk</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="p">])</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">face_info</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Returns a list of FaceInfo objects associated with this person sorted by quality score</span>
|
||||
<span class="sd">"""Returns a list of FaceInfo objects associated with this person sorted by quality score</span>
|
||||
<span class="sd"> Highest quality face is result[0] and lowest quality face is result[n]</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">faces</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_faceinfo_person</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="p">]</span>
|
||||
<span class="k">return</span> <span class="nb">sorted</span><span class="p">(</span>
|
||||
@@ -106,30 +269,32 @@
|
||||
<span class="k">return</span> <span class="p">[]</span>
|
||||
|
||||
<div class="viewcode-block" id="PersonInfo.asdict"><a class="viewcode-back" href="../../reference.html#osxphotos.PersonInfo.asdict">[docs]</a> <span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Returns dictionary representation of class instance"""</span>
|
||||
<span class="sd">"""Returns dictionary representation of class instance"""</span>
|
||||
<span class="n">keyphoto</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">keyphoto</span><span class="o">.</span><span class="n">uuid</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">keyphoto</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="kc">None</span>
|
||||
<span class="k">return</span> <span class="p">{</span>
|
||||
<span class="s2">"uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
|
||||
<span class="s2">"name"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
|
||||
<span class="s2">"displayname"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">display_name</span><span class="p">,</span>
|
||||
<span class="s2">"keyface"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">keyface</span><span class="p">,</span>
|
||||
<span class="s2">"facecount"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">facecount</span><span class="p">,</span>
|
||||
<span class="s2">"keyphoto"</span><span class="p">:</span> <span class="n">keyphoto</span><span class="p">,</span>
|
||||
<span class="s2">"uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
|
||||
<span class="s2">"name"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
|
||||
<span class="s2">"displayname"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">display_name</span><span class="p">,</span>
|
||||
<span class="s2">"keyface"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">keyface</span><span class="p">,</span>
|
||||
<span class="s2">"facecount"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">facecount</span><span class="p">,</span>
|
||||
<span class="s2">"keyphoto"</span><span class="p">:</span> <span class="n">keyphoto</span><span class="p">,</span>
|
||||
<span class="p">}</span></div>
|
||||
|
||||
<div class="viewcode-block" id="PersonInfo.json"><a class="viewcode-back" href="../../reference.html#osxphotos.PersonInfo.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">"""Returns JSON representation of class instance"""</span>
|
||||
<span class="sd">"""Returns JSON representation of class instance"""</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></div>
|
||||
|
||||
<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">"PersonInfo(name=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">, display_name=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">display_name</span><span class="si">}</span><span class="s2">, uuid=</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">, facecount=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">facecount</span><span class="si">}</span><span class="s2">)"</span>
|
||||
<span class="k">return</span> <span class="sa">f</span><span class="s2">"PersonInfo(name=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">, display_name=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">display_name</span><span class="si">}</span><span class="s2">, uuid=</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">, facecount=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">facecount</span><span class="si">}</span><span class="s2">)"</span>
|
||||
|
||||
<span class="k">def</span> <span class="fm">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="p">)):</span>
|
||||
<span class="k">return</span> <span class="kc">False</span>
|
||||
|
||||
<span class="k">return</span> <span class="nb">all</span><span class="p">(</span>
|
||||
<span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="o">==</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="k">for</span> <span class="n">field</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"_db"</span><span class="p">,</span> <span class="s2">"_pk"</span><span class="p">]</span>
|
||||
<span class="k">return</span> <span class="p">(</span>
|
||||
<span class="nb">all</span><span class="p">(</span>
|
||||
<span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="o">==</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span>
|
||||
<span class="k">for</span> <span class="n">field</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"_db"</span><span class="p">,</span> <span class="s2">"_pk"</span><span class="p">]</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="p">))</span>
|
||||
<span class="k">else</span> <span class="kc">False</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="fm">__ne__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
|
||||
@@ -137,10 +302,10 @@
|
||||
|
||||
|
||||
<span class="k">class</span> <span class="nc">FaceInfo</span><span class="p">:</span>
|
||||
<span class="sd">"""Info about a face in the Photos library"""</span>
|
||||
<span class="sd">"""Info about a face in the Photos library"""</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">db</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">pk</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="sd">"""Creates a new FaceInfo instance</span>
|
||||
<span class="sd">"""Creates a new FaceInfo instance</span>
|
||||
|
||||
<span class="sd"> Arguments:</span>
|
||||
<span class="sd"> db: instance of PhotosDB object</span>
|
||||
@@ -148,95 +313,59 @@
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> FaceInfo instance</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_db</span> <span class="o">=</span> <span class="n">db</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_pk</span> <span class="o">=</span> <span class="n">pk</span>
|
||||
|
||||
<span class="n">face</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_faceinfo_pk</span><span class="p">[</span><span class="n">pk</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_info</span> <span class="o">=</span> <span class="n">face</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">uuid</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"uuid"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"fullname"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"asset_uuid"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_person_pk</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"person"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">center_x</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"centerx"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">center_y</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"centery"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">mouth_x</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"mouthx"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">mouth_y</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"mouthy"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">left_eye_x</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"lefteyex"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">left_eye_y</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"lefteyey"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">right_eye_x</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"righteyex"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">right_eye_y</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"righteyey"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">size</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"size"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">quality</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"quality"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">source_width</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"sourcewidth"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">source_height</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"sourceheight"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">has_smile</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"has_smile"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">left_eye_closed</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"left_eye_closed"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">right_eye_closed</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"right_eye_closed"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">manual</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"manual"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">face_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"facetype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">age_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"agetype"</span><span class="p">]</span>
|
||||
<span class="c1"># self.bald_type = face["baldtype"]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">eye_makeup_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"eyemakeuptype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">eye_state</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"eyestate"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">facial_hair_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"facialhairtype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">gender_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"gendertype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">glasses_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"glassestype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">hair_color_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"haircolortype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">intrash</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"intrash"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">lip_makeup_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"lipmakeuptype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">smile_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"smiletype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">uuid</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"uuid"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"fullname"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"asset_uuid"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_person_pk</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"person"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">center_x</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"centerx"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">center_y</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"centery"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">size</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"size"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">quality</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"quality"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">source_width</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"sourcewidth"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">source_height</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"sourceheight"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">has_smile</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"has_smile"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">manual</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"manual"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">face_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"facetype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">age_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"agetype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">eye_makeup_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"eyemakeuptype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">eye_state</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"eyestate"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">facial_hair_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"facialhairtype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">gender_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"gendertype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">glasses_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"glassestype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">hair_color_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"haircolortype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">intrash</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"intrash"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">lip_makeup_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"lipmakeuptype"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">smile_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"smiletype"</span><span class="p">]</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">center</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Coordinates, in PIL format, for center of face</span>
|
||||
<span class="sd">"""Coordinates, in PIL format, for center of face</span>
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> tuple of coordinates in form (x, y)</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_make_point</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_y</span><span class="p">))</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">size_pixels</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Size of face in pixels (centered around center_x, center_y)</span>
|
||||
<span class="sd">"""Size of face in pixels (centered around center_x, center_y)</span>
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> size, in int pixels, of a circle drawn around the center of the face</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="n">photo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span>
|
||||
<span class="n">size_reference</span> <span class="o">=</span> <span class="n">photo</span><span class="o">.</span><span class="n">width</span> <span class="k">if</span> <span class="n">photo</span><span class="o">.</span><span class="n">width</span> <span class="o">></span> <span class="n">photo</span><span class="o">.</span><span class="n">height</span> <span class="k">else</span> <span class="n">photo</span><span class="o">.</span><span class="n">height</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">size</span> <span class="o">*</span> <span class="n">size_reference</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">mouth</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Coordinates, in PIL format, for mouth position</span>
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> tuple of coordinates in form (x, y)</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_make_point_with_rotation</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">mouth_x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">mouth_y</span><span class="p">))</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">left_eye</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Coordinates, in PIL format, for left eye position</span>
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> tuple of coordinates in form (x, y)</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_make_point_with_rotation</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">left_eye_x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">left_eye_y</span><span class="p">))</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">right_eye</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Coordinates, in PIL format, for right eye position</span>
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> tuple of coordinates in form (x, y)</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_make_point_with_rotation</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">right_eye_x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">right_eye_y</span><span class="p">))</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">person_info</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""PersonInfo instance for person associated with this face"""</span>
|
||||
<span class="sd">"""PersonInfo instance for person associated with this face"""</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">_person</span>
|
||||
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
|
||||
@@ -245,18 +374,18 @@
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">photo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""PhotoInfo instance associated with this face"""</span>
|
||||
<span class="sd">"""PhotoInfo instance associated with this face"""</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">_photo</span>
|
||||
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_photo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">get_photo</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</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="ow">is</span> <span class="kc">None</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">"Could not get photo for uuid: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span><span class="si">}</span><span class="s2">"</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">"Could not get photo for uuid: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</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">_photo</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">mwg_rs_area</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Get coordinates for Metadata Working Group Region Area.</span>
|
||||
<span class="sd">"""Get coordinates for Metadata Working Group Region Area.</span>
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> MWG_RS_Area named tuple with x, y, h, w where:</span>
|
||||
@@ -267,7 +396,7 @@
|
||||
|
||||
<span class="sd"> Reference:</span>
|
||||
<span class="sd"> https://photo.stackexchange.com/questions/106410/how-does-xmp-define-the-face-region</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_y</span>
|
||||
<span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fix_orientation</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span>
|
||||
|
||||
@@ -282,7 +411,7 @@
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">mpri_reg_rect</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Get coordinates for Microsoft Photo Region Rectangle.</span>
|
||||
<span class="sd">"""Get coordinates for Microsoft Photo Region Rectangle.</span>
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> MPRI_Reg_Rect named tuple with x, y, h, w where:</span>
|
||||
@@ -293,7 +422,7 @@
|
||||
|
||||
<span class="sd"> Reference:</span>
|
||||
<span class="sd"> https://docs.microsoft.com/en-us/windows/win32/wic/-wic-people-tagging</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_y</span>
|
||||
<span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fix_orientation</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span>
|
||||
|
||||
@@ -311,13 +440,13 @@
|
||||
<span class="k">return</span> <span class="n">MPRI_Reg_Rect</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">face_rect</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Get face rectangle coordinates for current version of the associated image</span>
|
||||
<span class="sd">"""Get face rectangle coordinates for current version of the associated image</span>
|
||||
<span class="sd"> If image has been edited, rectangle applies to edited version, otherwise original version</span>
|
||||
<span class="sd"> Coordinates in format and reference frame used by PIL</span>
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> list [(x0, x1), (y0, y1)] of coordinates in reference frame used by PIL</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="n">photo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span>
|
||||
<span class="n">size_reference</span> <span class="o">=</span> <span class="n">photo</span><span class="o">.</span><span class="n">width</span> <span class="k">if</span> <span class="n">photo</span><span class="o">.</span><span class="n">width</span> <span class="o">></span> <span class="n">photo</span><span class="o">.</span><span class="n">height</span> <span class="k">else</span> <span class="n">photo</span><span class="o">.</span><span class="n">height</span>
|
||||
<span class="n">radius</span> <span class="o">=</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">size</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span> <span class="o">*</span> <span class="n">size_reference</span>
|
||||
@@ -327,34 +456,34 @@
|
||||
<span class="k">return</span> <span class="p">[(</span><span class="n">x0</span><span class="p">,</span> <span class="n">y0</span><span class="p">),</span> <span class="p">(</span><span class="n">x1</span><span class="p">,</span> <span class="n">y1</span><span class="p">)]</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">roll_pitch_yaw</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Roll, pitch, yaw of face in radians as tuple"""</span>
|
||||
<span class="sd">"""Roll, pitch, yaw of face in radians as tuple"""</span>
|
||||
<span class="n">info</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span>
|
||||
<span class="n">roll</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">"roll"</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">info</span><span class="p">[</span><span class="s2">"roll"</span><span class="p">]</span>
|
||||
<span class="n">pitch</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">"pitch"</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">info</span><span class="p">[</span><span class="s2">"pitch"</span><span class="p">]</span>
|
||||
<span class="n">yaw</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">"yaw"</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">info</span><span class="p">[</span><span class="s2">"yaw"</span><span class="p">]</span>
|
||||
<span class="n">roll</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">"roll"</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">info</span><span class="p">[</span><span class="s2">"roll"</span><span class="p">]</span>
|
||||
<span class="n">pitch</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">"pitch"</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">info</span><span class="p">[</span><span class="s2">"pitch"</span><span class="p">]</span>
|
||||
<span class="n">yaw</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">"yaw"</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">info</span><span class="p">[</span><span class="s2">"yaw"</span><span class="p">]</span>
|
||||
|
||||
<span class="k">return</span> <span class="p">(</span><span class="n">roll</span><span class="p">,</span> <span class="n">pitch</span><span class="p">,</span> <span class="n">yaw</span><span class="p">)</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">roll</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Return roll angle in radians of the face region"""</span>
|
||||
<span class="sd">"""Return roll angle in radians of the face region"""</span>
|
||||
<span class="n">roll</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">roll_pitch_yaw</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">roll</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">pitch</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Return pitch angle in radians of the face region"""</span>
|
||||
<span class="sd">"""Return pitch angle in radians of the face region"""</span>
|
||||
<span class="n">_</span><span class="p">,</span> <span class="n">pitch</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">roll_pitch_yaw</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">pitch</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">yaw</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Return yaw angle in radians of the face region"""</span>
|
||||
<span class="sd">"""Return yaw angle in radians of the face region"""</span>
|
||||
<span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">yaw</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">roll_pitch_yaw</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">yaw</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">_fix_orientation</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">xy</span><span class="p">):</span>
|
||||
<span class="sd">"""Translate an (x, y) tuple based on image orientation</span>
|
||||
<span class="sd">"""Translate an (x, y) tuple based on image orientation</span>
|
||||
|
||||
<span class="sd"> Arguments:</span>
|
||||
<span class="sd"> xy: tuple of (x, y) coordinates for point to translate</span>
|
||||
@@ -362,7 +491,7 @@
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> (x, y) tuple of translated coordinates</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="c1"># Reference: https://github.com/neilpa/phace/blob/7594776480505d0c389688a42099c94ac5d34f3f/cmd/phace/draw.go#L79-L94</span>
|
||||
|
||||
<span class="n">orientation</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">orientation</span>
|
||||
@@ -386,15 +515,15 @@
|
||||
<span class="k">elif</span> <span class="n">orientation</span> <span class="o">==</span> <span class="mi">8</span><span class="p">:</span>
|
||||
<span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span>
|
||||
<span class="k">elif</span> <span class="n">orientation</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
|
||||
<span class="c1"># set by osxphotos if adjusted orientation cannot be read, assume it's 1</span>
|
||||
<span class="c1"># set by osxphotos if adjusted orientation cannot be read, assume it's 1</span>
|
||||
<span class="n">y</span> <span class="o">=</span> <span class="mf">1.0</span> <span class="o">-</span> <span class="n">y</span>
|
||||
<span class="k">else</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">"Unhandled orientation: </span><span class="si">{</span><span class="n">orientation</span><span class="si">}</span><span class="s2">"</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">"Unhandled orientation: </span><span class="si">{</span><span class="n">orientation</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">return</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">_make_point</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">xy</span><span class="p">):</span>
|
||||
<span class="sd">"""Translate an (x, y) tuple based on image orientation</span>
|
||||
<span class="sd">"""Translate an (x, y) tuple based on image orientation</span>
|
||||
<span class="sd"> and convert to image coordinates</span>
|
||||
|
||||
<span class="sd"> Arguments:</span>
|
||||
@@ -403,7 +532,7 @@
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> (x, y) tuple of translated coordinates in pixels in PIL format/reference frame</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="c1"># Reference: https://github.com/neilpa/phace/blob/7594776480505d0c389688a42099c94ac5d34f3f/cmd/phace/draw.go#L79-L94</span>
|
||||
|
||||
<span class="n">orientation</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">orientation</span>
|
||||
@@ -415,7 +544,7 @@
|
||||
<span class="k">return</span> <span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">x</span> <span class="o">*</span> <span class="n">dx</span><span class="p">),</span> <span class="nb">int</span><span class="p">(</span><span class="n">y</span> <span class="o">*</span> <span class="n">dy</span><span class="p">))</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">_make_point_with_rotation</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">xy</span><span class="p">):</span>
|
||||
<span class="sd">"""Translate an (x, y) tuple based on image orientation and rotation</span>
|
||||
<span class="sd">"""Translate an (x, y) tuple based on image orientation and rotation</span>
|
||||
<span class="sd"> and convert to image coordinates</span>
|
||||
|
||||
<span class="sd"> Arguments:</span>
|
||||
@@ -424,7 +553,7 @@
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> (x, y) tuple of translated coordinates in pixels in PIL format/reference frame</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="c1"># convert to image coordinates</span>
|
||||
<span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_make_point</span><span class="p">(</span><span class="n">xy</span><span class="p">)</span>
|
||||
@@ -437,70 +566,58 @@
|
||||
<span class="k">return</span> <span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">xr</span><span class="p">),</span> <span class="nb">int</span><span class="p">(</span><span class="n">yr</span><span class="p">))</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">"""Returns dict representation of class instance"""</span>
|
||||
<span class="sd">"""Returns dict representation of class instance"""</span>
|
||||
<span class="n">roll</span><span class="p">,</span> <span class="n">pitch</span><span class="p">,</span> <span class="n">yaw</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">roll_pitch_yaw</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="p">{</span>
|
||||
<span class="s2">"_pk"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="p">,</span>
|
||||
<span class="s2">"uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
|
||||
<span class="s2">"name"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
|
||||
<span class="s2">"asset_uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span><span class="p">,</span>
|
||||
<span class="s2">"_person_pk"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_person_pk</span><span class="p">,</span>
|
||||
<span class="s2">"center_x"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="p">,</span>
|
||||
<span class="s2">"center_y"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_y</span><span class="p">,</span>
|
||||
<span class="s2">"center"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">center</span><span class="p">,</span>
|
||||
<span class="s2">"mouth_x"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mouth_x</span><span class="p">,</span>
|
||||
<span class="s2">"mouth_y"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mouth_y</span><span class="p">,</span>
|
||||
<span class="s2">"mouth"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mouth</span><span class="p">,</span>
|
||||
<span class="s2">"left_eye_x"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">left_eye_x</span><span class="p">,</span>
|
||||
<span class="s2">"left_eye_y"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">left_eye_y</span><span class="p">,</span>
|
||||
<span class="s2">"left_eye"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">left_eye</span><span class="p">,</span>
|
||||
<span class="s2">"right_eye_x"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">right_eye_x</span><span class="p">,</span>
|
||||
<span class="s2">"right_eye_y"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">right_eye_y</span><span class="p">,</span>
|
||||
<span class="s2">"right_eye"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">right_eye</span><span class="p">,</span>
|
||||
<span class="s2">"size"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">size</span><span class="p">,</span>
|
||||
<span class="s2">"face_rect"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">face_rect</span><span class="p">(),</span>
|
||||
<span class="s2">"mpri_reg_rect"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mpri_reg_rect</span><span class="o">.</span><span class="n">_asdict</span><span class="p">(),</span>
|
||||
<span class="s2">"mwg_rs_area"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mwg_rs_area</span><span class="o">.</span><span class="n">_asdict</span><span class="p">(),</span>
|
||||
<span class="s2">"roll"</span><span class="p">:</span> <span class="n">roll</span><span class="p">,</span>
|
||||
<span class="s2">"pitch"</span><span class="p">:</span> <span class="n">pitch</span><span class="p">,</span>
|
||||
<span class="s2">"yaw"</span><span class="p">:</span> <span class="n">yaw</span><span class="p">,</span>
|
||||
<span class="s2">"quality"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">quality</span><span class="p">,</span>
|
||||
<span class="s2">"source_width"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">source_width</span><span class="p">,</span>
|
||||
<span class="s2">"source_height"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">source_height</span><span class="p">,</span>
|
||||
<span class="s2">"has_smile"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">has_smile</span><span class="p">,</span>
|
||||
<span class="s2">"left_eye_closed"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">left_eye_closed</span><span class="p">,</span>
|
||||
<span class="s2">"right_eye_closed"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">right_eye_closed</span><span class="p">,</span>
|
||||
<span class="s2">"manual"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">manual</span><span class="p">,</span>
|
||||
<span class="s2">"face_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">face_type</span><span class="p">,</span>
|
||||
<span class="s2">"age_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">age_type</span><span class="p">,</span>
|
||||
<span class="c1"># "bald_type": self.bald_type,</span>
|
||||
<span class="s2">"eye_makeup_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">eye_makeup_type</span><span class="p">,</span>
|
||||
<span class="s2">"eye_state"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">eye_state</span><span class="p">,</span>
|
||||
<span class="s2">"facial_hair_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">facial_hair_type</span><span class="p">,</span>
|
||||
<span class="s2">"gender_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">gender_type</span><span class="p">,</span>
|
||||
<span class="s2">"glasses_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">glasses_type</span><span class="p">,</span>
|
||||
<span class="s2">"hair_color_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">hair_color_type</span><span class="p">,</span>
|
||||
<span class="s2">"intrash"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">intrash</span><span class="p">,</span>
|
||||
<span class="s2">"lip_makeup_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">lip_makeup_type</span><span class="p">,</span>
|
||||
<span class="s2">"smile_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">smile_type</span><span class="p">,</span>
|
||||
<span class="s2">"_pk"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="p">,</span>
|
||||
<span class="s2">"uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
|
||||
<span class="s2">"name"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
|
||||
<span class="s2">"asset_uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span><span class="p">,</span>
|
||||
<span class="s2">"_person_pk"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_person_pk</span><span class="p">,</span>
|
||||
<span class="s2">"center_x"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="p">,</span>
|
||||
<span class="s2">"center_y"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_y</span><span class="p">,</span>
|
||||
<span class="s2">"center"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">center</span><span class="p">,</span>
|
||||
<span class="s2">"size"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">size</span><span class="p">,</span>
|
||||
<span class="s2">"face_rect"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">face_rect</span><span class="p">(),</span>
|
||||
<span class="s2">"mpri_reg_rect"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mpri_reg_rect</span><span class="o">.</span><span class="n">_asdict</span><span class="p">(),</span>
|
||||
<span class="s2">"mwg_rs_area"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mwg_rs_area</span><span class="o">.</span><span class="n">_asdict</span><span class="p">(),</span>
|
||||
<span class="s2">"roll"</span><span class="p">:</span> <span class="n">roll</span><span class="p">,</span>
|
||||
<span class="s2">"pitch"</span><span class="p">:</span> <span class="n">pitch</span><span class="p">,</span>
|
||||
<span class="s2">"yaw"</span><span class="p">:</span> <span class="n">yaw</span><span class="p">,</span>
|
||||
<span class="s2">"quality"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">quality</span><span class="p">,</span>
|
||||
<span class="s2">"source_width"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">source_width</span><span class="p">,</span>
|
||||
<span class="s2">"source_height"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">source_height</span><span class="p">,</span>
|
||||
<span class="s2">"has_smile"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">has_smile</span><span class="p">,</span>
|
||||
<span class="s2">"manual"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">manual</span><span class="p">,</span>
|
||||
<span class="s2">"face_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">face_type</span><span class="p">,</span>
|
||||
<span class="s2">"age_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">age_type</span><span class="p">,</span>
|
||||
<span class="s2">"eye_makeup_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">eye_makeup_type</span><span class="p">,</span>
|
||||
<span class="s2">"eye_state"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">eye_state</span><span class="p">,</span>
|
||||
<span class="s2">"facial_hair_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">facial_hair_type</span><span class="p">,</span>
|
||||
<span class="s2">"gender_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">gender_type</span><span class="p">,</span>
|
||||
<span class="s2">"glasses_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">glasses_type</span><span class="p">,</span>
|
||||
<span class="s2">"hair_color_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">hair_color_type</span><span class="p">,</span>
|
||||
<span class="s2">"intrash"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">intrash</span><span class="p">,</span>
|
||||
<span class="s2">"lip_makeup_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">lip_makeup_type</span><span class="p">,</span>
|
||||
<span class="s2">"smile_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">smile_type</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="sd">"""Return JSON representation of FaceInfo instance"""</span>
|
||||
<span class="sd">"""Return JSON representation of FaceInfo instance"""</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="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">"FaceInfo(uuid=</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">, center_x=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="si">}</span><span class="s2">, center_y = </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">center_y</span><span class="si">}</span><span class="s2">, size=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">size</span><span class="si">}</span><span class="s2">, person=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">, asset_uuid=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span><span class="si">}</span><span class="s2">)"</span>
|
||||
<span class="k">return</span> <span class="sa">f</span><span class="s2">"FaceInfo(uuid=</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">, center_x=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="si">}</span><span class="s2">, center_y = </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">center_y</span><span class="si">}</span><span class="s2">, size=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">size</span><span class="si">}</span><span class="s2">, person=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">, asset_uuid=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span><span class="si">}</span><span class="s2">)"</span>
|
||||
|
||||
<span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="sa">f</span><span class="s2">"FaceInfo(db=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="si">}</span><span class="s2">, pk=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="si">}</span><span class="s2">)"</span>
|
||||
<span class="k">return</span> <span class="sa">f</span><span class="s2">"FaceInfo(db=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="si">}</span><span class="s2">, pk=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="si">}</span><span class="s2">)"</span>
|
||||
|
||||
<span class="k">def</span> <span class="fm">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="p">)):</span>
|
||||
<span class="k">return</span> <span class="kc">False</span>
|
||||
|
||||
<span class="k">return</span> <span class="nb">all</span><span class="p">(</span>
|
||||
<span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="o">==</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="k">for</span> <span class="n">field</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"_db"</span><span class="p">,</span> <span class="s2">"_pk"</span><span class="p">]</span>
|
||||
<span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="o">==</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="k">for</span> <span class="n">field</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"_db"</span><span class="p">,</span> <span class="s2">"_pk"</span><span class="p">]</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="fm">__ne__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
|
||||
@@ -508,7 +625,7 @@
|
||||
|
||||
|
||||
<span class="k">def</span> <span class="nf">rotate_image_point</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">xmid</span><span class="p">,</span> <span class="n">ymid</span><span class="p">,</span> <span class="n">angle</span><span class="p">):</span>
|
||||
<span class="sd">"""rotate image point about xm, ym by angle in radians</span>
|
||||
<span class="sd">"""rotate image point about xm, ym by angle in radians</span>
|
||||
|
||||
<span class="sd"> Arguments:</span>
|
||||
<span class="sd"> x: x coordinate of point to rotate</span>
|
||||
@@ -520,7 +637,7 @@
|
||||
|
||||
<span class="sd"> Returns:</span>
|
||||
<span class="sd"> tuple of rotated points (xr, yr)</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="c1"># translate point relative to the mid point</span>
|
||||
<span class="n">x</span> <span class="o">=</span> <span class="n">x</span> <span class="o">-</span> <span class="n">xmid</span>
|
||||
<span class="n">y</span> <span class="o">=</span> <span class="n">y</span> <span class="o">-</span> <span class="n">ymid</span>
|
||||
@@ -535,72 +652,45 @@
|
||||
|
||||
<span class="k">return</span> <span class="p">(</span><span class="n">xr</span><span class="p">,</span> <span class="n">yr</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 © 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">
|
||||
©2021, Rhet Turnbull.
|
||||
<aside class="toc-drawer no-toc">
|
||||
|
||||
|
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.4.0</a>
|
||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</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>
|
||||
@@ -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.49.2 documentation</title>
|
||||
<title>osxphotos.photoexporter - osxphotos 0.50.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.49.2 documentation</div></a>
|
||||
<a href="../../index.html"><div class="brand">osxphotos 0.50.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.49.2 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.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">
|
||||
@@ -240,7 +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">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="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="p">(</span>
|
||||
<span class="n">hexdigest</span><span class="p">,</span>
|
||||
<span class="n">increment_filename</span><span class="p">,</span>
|
||||
<span class="n">increment_filename_with_count</span><span class="p">,</span>
|
||||
<span class="n">lineno</span><span class="p">,</span>
|
||||
<span class="n">list_directory</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span>
|
||||
<span class="s2">"ExportError"</span><span class="p">,</span>
|
||||
@@ -684,7 +690,7 @@
|
||||
<span class="n">src</span> <span class="o">=</span> <span class="n">staged_files</span><span class="o">.</span><span class="n">edited</span> <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">edited</span> <span class="k">else</span> <span class="n">staged_files</span><span class="o">.</span><span class="n">original</span>
|
||||
|
||||
<span class="c1"># get the right destination path depending on options.update, etc.</span>
|
||||
<span class="n">dest</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_dest_path</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">options</span><span class="p">)</span>
|
||||
<span class="n">dest</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_dest_path</span><span class="p">(</span><span class="n">dest</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_render_options</span><span class="o">.</span><span class="n">filepath</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span>
|
||||
<span class="n">all_results</span> <span class="o">=</span> <span class="n">ExportResults</span><span class="p">()</span>
|
||||
@@ -838,12 +844,11 @@
|
||||
<span class="k">return</span> <span class="n">edited_filename</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">_get_dest_path</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span> <span class="n">src</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">dest</span><span class="p">:</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="n">ExportOptions</span>
|
||||
<span class="bp">self</span><span class="p">,</span> <span class="n">dest</span><span class="p">:</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="n">ExportOptions</span>
|
||||
<span class="p">)</span> <span class="o">-></span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">:</span>
|
||||
<span class="sd">"""If destination exists find match in ExportDB, on disk, or add (1), (2), and so on to filename to get a valid destination</span>
|
||||
|
||||
<span class="sd"> Args:</span>
|
||||
<span class="sd"> src (str): source file path</span>
|
||||
<span class="sd"> dest (str): destination path</span>
|
||||
<span class="sd"> options (ExportOptions): Export options</span>
|
||||
|
||||
@@ -859,6 +864,10 @@
|
||||
<span class="sa">f</span><span class="s2">"destination exists (</span><span class="si">{</span><span class="n">dest</span><span class="si">}</span><span class="s2">); overwrite=</span><span class="si">{</span><span class="n">options</span><span class="o">.</span><span class="n">overwrite</span><span class="si">}</span><span class="s2">, increment=</span><span class="si">{</span><span class="n">options</span><span class="o">.</span><span class="n">increment</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># if overwrite, we don't care if the file exists or not</span>
|
||||
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">overwrite</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">dest</span>
|
||||
|
||||
<span class="c1"># if not update or overwrite, check to see if file exists and if so, add (1), (2), etc</span>
|
||||
<span class="c1"># until we find one that works</span>
|
||||
<span class="c1"># Photos checks the stem and adds (1), (2), etc which avoids collision with sidecars</span>
|
||||
@@ -871,29 +880,36 @@
|
||||
<span class="k">return</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">increment_filename</span><span class="p">(</span><span class="n">dest</span><span class="p">))</span>
|
||||
|
||||
<span class="c1"># if update and file exists, need to check to see if it's the right file by checking export db</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="n">options</span><span class="o">.</span><span class="n">update</span> <span class="ow">or</span> <span class="n">options</span><span class="o">.</span><span class="n">force_update</span><span class="p">)</span> <span class="ow">and</span> <span class="n">dest</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="ow">and</span> <span class="n">src</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">update</span> <span class="ow">or</span> <span class="n">options</span><span class="o">.</span><span class="n">force_update</span><span class="p">:</span>
|
||||
<span class="n">export_db</span> <span class="o">=</span> <span class="n">options</span><span class="o">.</span><span class="n">export_db</span>
|
||||
<span class="c1"># destination exists, check to see if destination is the right UUID</span>
|
||||
<span class="n">dest_uuid</span> <span class="o">=</span> <span class="n">export_db</span><span class="o">.</span><span class="n">get_uuid_for_file</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">dest_uuid</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">uuid</span><span class="p">:</span>
|
||||
<span class="c1"># not the right file, find the right one</span>
|
||||
<span class="c1"># find files that match "dest_name (*.ext" (e.g. "dest_name (1).jpg", "dest_name (2).jpg)", ...)</span>
|
||||
<span class="n">dest_files</span> <span class="o">=</span> <span class="n">list_directory</span><span class="p">(</span>
|
||||
<span class="n">dest</span><span class="o">.</span><span class="n">parent</span><span class="p">,</span>
|
||||
<span class="n">startswith</span><span class="o">=</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">dest</span><span class="o">.</span><span class="n">stem</span><span class="si">}</span><span class="s2"> ("</span><span class="p">,</span>
|
||||
<span class="n">endswith</span><span class="o">=</span><span class="n">dest</span><span class="o">.</span><span class="n">suffix</span><span class="p">,</span>
|
||||
<span class="n">include_path</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">for</span> <span class="n">file_</span> <span class="ow">in</span> <span class="n">dest_files</span><span class="p">:</span>
|
||||
<span class="n">dest_uuid</span> <span class="o">=</span> <span class="n">export_db</span><span class="o">.</span><span class="n">get_uuid_for_file</span><span class="p">(</span><span class="n">file_</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">dest_uuid</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">uuid</span><span class="p">:</span>
|
||||
<span class="n">dest</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">file_</span><span class="p">)</span>
|
||||
<span class="k">break</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="c1"># increment the destination file</span>
|
||||
<span class="n">dest</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">increment_filename</span><span class="p">(</span><span class="n">dest</span><span class="p">))</span>
|
||||
<span class="k">if</span> <span class="n">dest_uuid</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">dest</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
|
||||
<span class="c1"># destination doesn't exist in export db and doesn't exist on disk</span>
|
||||
<span class="c1"># so we can just use it</span>
|
||||
<span class="k">return</span> <span class="n">dest</span>
|
||||
|
||||
<span class="c1"># either dest was updated in the if clause above or not updated at all</span>
|
||||
<span class="k">if</span> <span class="n">dest_uuid</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">uuid</span><span class="p">:</span>
|
||||
<span class="c1"># destination is the right file</span>
|
||||
<span class="k">return</span> <span class="n">dest</span>
|
||||
|
||||
<span class="c1"># either dest_uuid is wrong or file exists and there's no associated UUID, so find a name that matches</span>
|
||||
<span class="c1"># or create a new name if no match</span>
|
||||
<span class="c1"># find files that match "dest_name (*.ext" (e.g. "dest_name (1).jpg", "dest_name (2).jpg)", ...)</span>
|
||||
<span class="c1"># first, find all matching files in export db and see if there's a match</span>
|
||||
<span class="k">if</span> <span class="n">dest_target</span> <span class="o">:=</span> <span class="n">export_db</span><span class="o">.</span><span class="n">get_target_for_file</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span> <span class="n">dest</span><span class="p">):</span>
|
||||
<span class="c1"># there's a match so use that</span>
|
||||
<span class="k">return</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">dest_target</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># no match so need to create a new name</span>
|
||||
<span class="c1"># increment the destination file until we find one that doesn't exist and doesn't match another uuid in the database</span>
|
||||
<span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
|
||||
<span class="n">dest</span><span class="p">,</span> <span class="n">count</span> <span class="o">=</span> <span class="n">increment_filename_with_count</span><span class="p">(</span><span class="n">dest</span><span class="p">,</span> <span class="n">count</span><span class="p">)</span>
|
||||
<span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
|
||||
<span class="k">while</span> <span class="n">export_db</span><span class="o">.</span><span class="n">get_uuid_for_file</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">dest</span><span class="p">,</span> <span class="n">count</span> <span class="o">=</span> <span class="n">increment_filename_with_count</span><span class="p">(</span><span class="n">dest</span><span class="p">,</span> <span class="n">count</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># fail safe...I can't think of a case that gets here</span>
|
||||
<span class="k">return</span> <span class="n">dest</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">_should_update_photo</span><span class="p">(</span>
|
||||
@@ -1232,9 +1248,9 @@
|
||||
|
||||
<span class="k">def</span> <span class="nf">_export_photo</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span>
|
||||
<span class="n">src</span><span class="p">,</span>
|
||||
<span class="n">dest</span><span class="p">,</span>
|
||||
<span class="n">options</span><span class="p">,</span>
|
||||
<span class="n">src</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
|
||||
<span class="n">dest</span><span class="p">:</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">,</span>
|
||||
<span class="n">options</span><span class="p">:</span> <span class="n">ExportOptions</span><span class="p">,</span>
|
||||
<span class="p">):</span>
|
||||
<span class="sd">"""Helper function for export()</span>
|
||||
<span class="sd"> Does the actual copy or hardlink taking the appropriate</span>
|
||||
@@ -1256,6 +1272,7 @@
|
||||
<span class="sd"> ValueError if export_as_hardlink and convert_to_jpeg both True</span>
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="n">verbose</span> <span class="o">=</span> <span class="n">options</span><span class="o">.</span><span class="n">verbose</span> <span class="ow">or</span> <span class="bp">self</span><span class="o">.</span><span class="n">_verbose</span>
|
||||
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">export_as_hardlink</span> <span class="ow">and</span> <span class="n">options</span><span class="o">.</span><span class="n">convert_to_jpeg</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
|
||||
<span class="s2">"export_as_hardlink and convert_to_jpeg cannot both be True"</span>
|
||||
@@ -1345,6 +1362,9 @@
|
||||
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">fileutil</span><span class="o">.</span><span class="n">copy</span><span class="p">(</span><span class="n">src</span><span class="p">,</span> <span class="n">dest_str</span><span class="p">)</span>
|
||||
<span class="n">verbose</span><span class="p">(</span>
|
||||
<span class="sa">f</span><span class="s2">"Exported </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_filename</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">original_filename</span><span class="p">)</span><span class="si">}</span><span class="s2"> to </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_filepath</span><span class="p">(</span><span class="n">dest_str</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="n">ExportError</span><span class="p">(</span>
|
||||
<span class="sa">f</span><span class="s2">"Error copying file </span><span class="si">{</span><span class="n">src</span><span class="si">}</span><span class="s2"> to </span><span class="si">{</span><span class="n">dest_str</span><span class="si">}</span><span class="s2">: </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">lineno</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="si">}</span><span class="s2">)"</span>
|
||||
@@ -1640,7 +1660,7 @@
|
||||
<span class="p">)</span> <span class="o">-></span> <span class="n">ExportResults</span><span class="p">:</span>
|
||||
<span class="sd">"""Write exif metadata to src file using exiftool</span>
|
||||
|
||||
<span class="sd"> Caution: This method modifies *src*, not *dest*, </span>
|
||||
<span class="sd"> Caution: This method modifies *src*, not *dest*,</span>
|
||||
<span class="sd"> so src must be a copy of the original file if you don't want the source modified;</span>
|
||||
<span class="sd"> it also does not write to dest (dest is the intended destination for purposes of</span>
|
||||
<span class="sd"> referencing the export database. This allows the exiftool update to be done on the</span>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>osxphotos.photoinfo - osxphotos 0.48.7 documentation</title>
|
||||
<title>osxphotos.photoinfo - osxphotos 0.50.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.7 documentation</div></a>
|
||||
<a href="../../index.html"><div class="brand">osxphotos 0.50.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.7 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.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">
|
||||
@@ -199,6 +199,8 @@
|
||||
<span class="sd">Represents a single photo in the Photos library and provides access to the photo's attributes</span>
|
||||
<span class="sd">PhotosDB.photos() returns a list of PhotoInfo objects</span>
|
||||
<span class="sd">"""</span>
|
||||
|
||||
<span class="kn">import</span> <span class="nn">contextlib</span>
|
||||
<span class="kn">import</span> <span class="nn">dataclasses</span>
|
||||
<span class="kn">import</span> <span class="nn">datetime</span>
|
||||
<span class="kn">import</span> <span class="nn">json</span>
|
||||
@@ -206,14 +208,16 @@
|
||||
<span class="kn">import</span> <span class="nn">os</span>
|
||||
<span class="kn">import</span> <span class="nn">os.path</span>
|
||||
<span class="kn">import</span> <span class="nn">pathlib</span>
|
||||
<span class="kn">import</span> <span class="nn">plistlib</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">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">Optional</span>
|
||||
|
||||
<span class="kn">import</span> <span class="nn">yaml</span>
|
||||
<span class="kn">from</span> <span class="nn">osxmetadata</span> <span class="kn">import</span> <span class="n">OSXMetaData</span>
|
||||
|
||||
<span class="kn">from</span> <span class="nn">._constants</span> <span class="kn">import</span> <span class="p">(</span>
|
||||
<span class="n">_DB_TABLE_NAMES</span><span class="p">,</span>
|
||||
<span class="n">_MOVIE_TYPE</span><span class="p">,</span>
|
||||
<span class="n">_PHOTO_TYPE</span><span class="p">,</span>
|
||||
<span class="n">_PHOTOS_4_ALBUM_KIND</span><span class="p">,</span>
|
||||
@@ -1568,6 +1572,30 @@
|
||||
<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>
|
||||
|
||||
<span class="nd">@cached_property</span>
|
||||
<span class="k">def</span> <span class="nf">cloud_metadata</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="n">Dict</span><span class="p">:</span>
|
||||
<span class="sd">"""Returns contents of ZCLOUDMASTERMEDIAMETADATA as dict"""</span>
|
||||
<span class="c1"># This is a large blob of data so don't load it unless requested</span>
|
||||
<span class="n">asset_table</span> <span class="o">=</span> <span class="n">_DB_TABLE_NAMES</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">_photos_ver</span><span class="p">][</span><span class="s2">"ASSET"</span><span class="p">]</span>
|
||||
<span class="n">sql_cloud_metadata</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"""</span>
|
||||
<span class="s2"> SELECT ZCLOUDMASTERMEDIAMETADATA.ZDATA</span>
|
||||
<span class="s2"> FROM ZCLOUDMASTERMEDIAMETADATA</span>
|
||||
<span class="s2"> JOIN ZCLOUDMASTER ON ZCLOUDMASTER.Z_PK = ZCLOUDMASTERMEDIAMETADATA.ZCLOUDMASTER</span>
|
||||
<span class="s2"> JOIN </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2"> on </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZMASTER = ZCLOUDMASTER.Z_PK</span>
|
||||
<span class="s2"> WHERE </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZUUID = ?</span>
|
||||
<span class="s2"> """</span>
|
||||
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_version</span> <span class="o"><=</span> <span class="n">_PHOTOS_4_VERSION</span><span class="p">:</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"cloud_metadata not implemented for this database version"</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="p">{}</span>
|
||||
|
||||
<span class="n">_</span><span class="p">,</span> <span class="n">cursor</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">get_db_connection</span><span class="p">()</span>
|
||||
<span class="n">metadata</span> <span class="o">=</span> <span class="p">{}</span>
|
||||
<span class="k">if</span> <span class="n">results</span> <span class="o">:=</span> <span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">sql_cloud_metadata</span><span class="p">,</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,))</span><span class="o">.</span><span class="n">fetchone</span><span class="p">():</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="n">metadata</span> <span class="o">=</span> <span class="n">plistlib</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">results</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
|
||||
<span class="k">return</span> <span class="n">metadata</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>
|
||||
|
||||
|
||||
@@ -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.7 documentation</title>
|
||||
<title>osxphotos.photosdb.photosdb - osxphotos 0.50.4 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.7 documentation</div></a>
|
||||
<a href="../../../index.html"><div class="brand">osxphotos 0.50.4 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.7 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.4 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">
|
||||
@@ -210,7 +210,7 @@
|
||||
<span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">OrderedDict</span>
|
||||
<span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Iterable</span>
|
||||
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timedelta</span><span class="p">,</span> <span class="n">timezone</span>
|
||||
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
|
||||
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
|
||||
<span class="kn">from</span> <span class="nn">unicodedata</span> <span class="kn">import</span> <span class="n">normalize</span>
|
||||
|
||||
<span class="kn">import</span> <span class="nn">bitmath</span>
|
||||
@@ -3054,16 +3054,16 @@
|
||||
|
||||
<div class="viewcode-block" id="PhotosDB.photos"><a class="viewcode-back" href="../../../reference.html#osxphotos.PhotosDB.photos">[docs]</a> <span class="k">def</span> <span class="nf">photos</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span>
|
||||
<span class="n">keywords</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">uuid</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">persons</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">albums</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">images</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">movies</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">from_date</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">to_date</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">intrash</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
|
||||
<span class="p">):</span>
|
||||
<span class="n">keywords</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="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">uuid</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="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">persons</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="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">albums</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="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">images</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">movies</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">from_date</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</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="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">intrash</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
|
||||
<span class="p">)</span> <span class="o">-></span> <span class="n">List</span><span class="p">[</span><span class="n">PhotoInfo</span><span class="p">]:</span>
|
||||
<span class="sd">"""Return a list of PhotoInfo objects</span>
|
||||
<span class="sd"> If called with no args, returns the entire database of photos</span>
|
||||
<span class="sd"> If called with args, returns photos matching the args (e.g. keywords, persons, etc.)</span>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>osxphotos.phototemplate - osxphotos 0.49.0 documentation</title>
|
||||
<title>osxphotos.phototemplate - osxphotos 0.50.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.49.0 documentation</div></a>
|
||||
<a href="../../index.html"><div class="brand">osxphotos 0.50.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.49.0 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.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">
|
||||
@@ -201,11 +201,12 @@
|
||||
<span class="kn">import</span> <span class="nn">locale</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">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>
|
||||
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Tuple</span>
|
||||
|
||||
<span class="kn">from</span> <span class="nn">textx</span> <span class="kn">import</span> <span class="n">TextXSyntaxError</span><span class="p">,</span> <span class="n">metamodel_from_file</span>
|
||||
|
||||
@@ -215,7 +216,7 @@
|
||||
<span class="kn">from</span> <span class="nn">.exiftool</span> <span class="kn">import</span> <span class="n">ExifToolCaching</span>
|
||||
<span class="kn">from</span> <span class="nn">.path_utils</span> <span class="kn">import</span> <span class="n">sanitize_dirname</span><span class="p">,</span> <span class="n">sanitize_filename</span><span class="p">,</span> <span class="n">sanitize_pathpart</span>
|
||||
<span class="kn">from</span> <span class="nn">.text_detection</span> <span class="kn">import</span> <span class="n">detect_text</span>
|
||||
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">expand_and_validate_filepath</span><span class="p">,</span> <span class="n">load_function</span>
|
||||
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">expand_and_validate_filepath</span><span class="p">,</span> <span class="n">load_function</span><span class="p">,</span> <span class="n">uuid_to_shortuuid</span>
|
||||
|
||||
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span>
|
||||
<span class="s2">"RenderOptions"</span><span class="p">,</span>
|
||||
@@ -336,6 +337,8 @@
|
||||
<span class="s2">"</span><span class="si">{exif.lens_model}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"Lens model from original photo's EXIF information as imported by Photos, e.g. 'iPhone 6s back camera 4.15mm f/2.2'"</span><span class="p">,</span>
|
||||
<span class="s2">"</span><span class="si">{moment}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"The moment title of the photo"</span><span class="p">,</span>
|
||||
<span class="s2">"</span><span class="si">{uuid}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'"</span><span class="p">,</span>
|
||||
<span class="s2">"</span><span class="si">{shortuuid}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"A shorter representation of photo's internal universally unique identifier (UUID) for the photo, "</span>
|
||||
<span class="o">+</span> <span class="s2">"a 22-character string unique to the photo, e.g. 'JYsxugP9UjetmCbBCHXcmu'"</span><span class="p">,</span>
|
||||
<span class="s2">"</span><span class="si">{id}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"A unique number for the photo based on its primary key in the Photos database. "</span>
|
||||
<span class="o">+</span> <span class="s2">"A sequential integer, e.g. 1, 2, 3...etc. Each asset associated with a photo (e.g. an image and Live Photo preview) will share the same id. "</span>
|
||||
<span class="o">+</span> <span class="s2">"May be formatted using a python string format code. "</span>
|
||||
@@ -344,21 +347,23 @@
|
||||
<span class="s2">"</span><span class="si">{album_seq}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"An integer, starting at 0, indicating the photo's index (sequence) in the containing album. "</span>
|
||||
<span class="o">+</span> <span class="s2">"Only valid when used in a '--filename' template and only when '</span><span class="si">{album}</span><span class="s2">' or '</span><span class="si">{folder_album}</span><span class="s2">' is used in the '--directory' template. "</span>
|
||||
<span class="o">+</span> <span class="s1">'For example </span><span class="se">\'</span><span class="s1">--directory "</span><span class="si">{folder_album}</span><span class="s1">" --filename "</span><span class="si">{album_seq}</span><span class="s1">_</span><span class="si">{original_name}</span><span class="s1">"</span><span class="se">\'</span><span class="s1">. '</span>
|
||||
<span class="o">+</span> <span class="s2">"To start counting at a value other than 0, append append a period and the starting value to the field name. "</span>
|
||||
<span class="o">+</span> <span class="s2">"For example, to start counting at 1 instead of 0: '</span><span class="si">{album_seq.1}</span><span class="s2">'. "</span>
|
||||
<span class="o">+</span> <span class="s2">"To start counting at a value other than 0, append append '(starting_value)' to the field name. "</span>
|
||||
<span class="o">+</span> <span class="s2">"For example, to start counting at 1 instead of 0: '{album_seq(1)}'. "</span>
|
||||
<span class="o">+</span> <span class="s2">"May be formatted using a python string format code. "</span>
|
||||
<span class="o">+</span> <span class="s2">"For example, to format as a 5-digit integer and pad with zeros, use '</span><span class="si">{album_seq:05d}</span><span class="s2">' which results in "</span>
|
||||
<span class="o">+</span> <span class="s2">"00000, 00001, 00002...etc. "</span>
|
||||
<span class="o">+</span> <span class="s2">"To format while also using a starting value: '{album_seq:05d(1)}' which results in 0001, 00002...etc."</span>
|
||||
<span class="o">+</span> <span class="s2">"This may result in incorrect sequences if you have duplicate albums with the same name; see also '</span><span class="si">{folder_album_seq}</span><span class="s2">'."</span><span class="p">,</span>
|
||||
<span class="s2">"</span><span class="si">{folder_album_seq}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"An integer, starting at 0, indicating the photo's index (sequence) in the containing album and folder path. "</span>
|
||||
<span class="o">+</span> <span class="s2">"Only valid when used in a '--filename' template and only when '</span><span class="si">{folder_album}</span><span class="s2">' is used in the '--directory' template. "</span>
|
||||
<span class="o">+</span> <span class="s1">'For example </span><span class="se">\'</span><span class="s1">--directory "</span><span class="si">{folder_album}</span><span class="s1">" --filename "</span><span class="si">{folder_album_seq}</span><span class="s1">_</span><span class="si">{original_name}</span><span class="s1">"</span><span class="se">\'</span><span class="s1">. '</span>
|
||||
<span class="o">+</span> <span class="s2">"To start counting at a value other than 0, append append a period and the starting value to the field name. "</span>
|
||||
<span class="o">+</span> <span class="s2">"For example, to start counting at 1 instead of 0: '</span><span class="si">{folder_album_seq.1}</span><span class="s2">' "</span>
|
||||
<span class="o">+</span> <span class="s2">"To start counting at a value other than 0, append '(starting_value)' to the field name. "</span>
|
||||
<span class="o">+</span> <span class="s2">"For example, to start counting at 1 instead of 0: '{folder_album_seq(1)}' "</span>
|
||||
<span class="o">+</span> <span class="s2">"May be formatted using a python string format code. "</span>
|
||||
<span class="o">+</span> <span class="s2">"For example, to format as a 5-digit integer and pad with zeros, use '</span><span class="si">{folder_album_seq:05d}</span><span class="s2">' which results in "</span>
|
||||
<span class="o">+</span> <span class="s2">"00000, 00001, 00002...etc. "</span>
|
||||
<span class="o">+</span> <span class="s2">"This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also '</span><span class="si">{album_seq}</span><span class="s2">'."</span><span class="p">,</span>
|
||||
<span class="o">+</span> <span class="s2">"To format while also using a starting value: '{folder_album_seq:05d(1)}' which results in 0001, 00002...etc."</span>
|
||||
<span class="o">+</span> <span class="s2">"This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also '</span><span class="si">{album_seq}</span><span class="s2">'. "</span><span class="p">,</span>
|
||||
<span class="s2">"</span><span class="si">{comma}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"A comma: ','"</span><span class="p">,</span>
|
||||
<span class="s2">"</span><span class="si">{semicolon}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"A semicolon: ';'"</span><span class="p">,</span>
|
||||
<span class="s2">"</span><span class="si">{questionmark}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"A question mark: '?'"</span><span class="p">,</span>
|
||||
@@ -418,6 +423,9 @@
|
||||
<span class="o">+</span> <span class="s2">"Note: this feature is not the same thing as Live Text in macOS Monterey, which osxphotos does not yet support."</span><span class="p">,</span>
|
||||
<span class="s2">"</span><span class="si">{shell_quote}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"Use in form '{shell_quote,TEMPLATE}'; quotes the rendered TEMPLATE value(s) for safe usage in the shell, e.g. My file.jpeg => 'My file.jpeg'; only adds quotes if needed."</span><span class="p">,</span>
|
||||
<span class="s2">"</span><span class="si">{strip}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"Use in form '{strip,TEMPLATE}'; strips whitespace from begining and end of rendered TEMPLATE value(s)."</span><span class="p">,</span>
|
||||
<span class="s2">"</span><span class="si">{format}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"Use in form, '{format:TYPE:FORMAT,TEMPLATE}'; converts TEMPLATE value to TYPE then formats the value "</span>
|
||||
<span class="o">+</span> <span class="s2">"using Python string formatting codes specified by FORMAT; TYPE is one of: 'int', 'float', or 'str'. "</span>
|
||||
<span class="s2">"For example, '{format:float:.1f,{exiftool:EXIF:FocalLength}}' will format focal length to 1 decimal place (e.g. '100.0'). "</span><span class="p">,</span>
|
||||
<span class="s2">"</span><span class="si">{function}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"Execute a python function from an external file and use return value as template substitution. "</span>
|
||||
<span class="o">+</span> <span class="s2">"Use in format: {function:file.py::function_name} where 'file.py' is the name of the python file and 'function_name' is the name of the function to call. "</span>
|
||||
<span class="o">+</span> <span class="s2">"The function will be passed the PhotoInfo object for the photo. "</span>
|
||||
@@ -435,6 +443,27 @@
|
||||
<span class="s2">"brackets"</span><span class="p">:</span> <span class="s2">"Enclose value in brackets, e.g. 'value' => '[value]'"</span><span class="p">,</span>
|
||||
<span class="s2">"shell_quote"</span><span class="p">:</span> <span class="s2">"Quotes the value for safe usage in the shell, e.g. My file.jpeg => 'My file.jpeg'; only adds quotes if needed."</span><span class="p">,</span>
|
||||
<span class="s2">"function"</span><span class="p">:</span> <span class="s2">"Run custom python function to filter value; use in format 'function:/path/to/file.py::function_name'. See example at https://github.com/RhetTbull/osxphotos/blob/master/examples/template_filter.py"</span><span class="p">,</span>
|
||||
<span class="s2">"split(x)"</span><span class="p">:</span> <span class="s2">"Split value into a list of values using x as delimiter, e.g. 'value1;value2' => ['value1', 'value2'] if used with split(;)."</span><span class="p">,</span>
|
||||
<span class="s2">"autosplit"</span><span class="p">:</span> <span class="s2">"Automatically split delimited string into separate values; will split strings delimited by comma, semicolon, or space, e.g. 'value1,value2' => ['value1', 'value2']."</span><span class="p">,</span>
|
||||
<span class="s2">"chop(x)"</span><span class="p">:</span> <span class="s2">"Remove x characters off the end of value, e.g. chop(1): 'Value' => 'Valu'; when applied to a list, chops characters from each list value, e.g. chop(1): ['travel', 'beach']=> ['trave', 'beac']."</span><span class="p">,</span>
|
||||
<span class="s2">"chomp(x)"</span><span class="p">:</span> <span class="s2">"Remove x characters from the beginning of value, e.g. chomp(1): ['Value'] => ['alue']; when applied to a list, removes characters from each list value, e.g. chomp(1): ['travel', 'beach']=> ['ravel', 'each']."</span><span class="p">,</span>
|
||||
<span class="s2">"sort"</span><span class="p">:</span> <span class="s2">"Sort list of values, e.g. ['c', 'b', 'a'] => ['a', 'b', 'c']."</span><span class="p">,</span>
|
||||
<span class="s2">"rsort"</span><span class="p">:</span> <span class="s2">"Sort list of values in reverse order, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a']."</span><span class="p">,</span>
|
||||
<span class="s2">"reverse"</span><span class="p">:</span> <span class="s2">"Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a']."</span><span class="p">,</span>
|
||||
<span class="s2">"uniq"</span><span class="p">:</span> <span class="s2">"Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b', 'c']."</span><span class="p">,</span>
|
||||
<span class="s2">"join(x)"</span><span class="p">:</span> <span class="s2">"Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] => 'a,b,c'; "</span>
|
||||
<span class="o">+</span> <span class="s2">"the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters."</span>
|
||||
<span class="o">+</span> <span class="s2">"May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. "</span>
|
||||
<span class="o">+</span> <span class="s2">"e.g. join(): ['a', 'b', 'c'] => 'abc'."</span><span class="p">,</span>
|
||||
<span class="s2">"append(x)"</span><span class="p">:</span> <span class="s2">"Append x to list of values, e.g. append(d): ['a', 'b', 'c'] => ['a', 'b', 'c', 'd']."</span><span class="p">,</span>
|
||||
<span class="s2">"prepend(x)"</span><span class="p">:</span> <span class="s2">"Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] => ['d', 'a', 'b', 'c']."</span><span class="p">,</span>
|
||||
<span class="s2">"remove(x)"</span><span class="p">:</span> <span class="s2">"Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] => ['a', 'c']."</span><span class="p">,</span>
|
||||
<span class="s2">"slice(start:stop:step)"</span><span class="p">:</span> <span class="s2">"Slice list using same semantics as Python's list slicing, "</span>
|
||||
<span class="o">+</span> <span class="s2">"e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] => ['b', 'd']; "</span>
|
||||
<span class="o">+</span> <span class="s2">"slice(1:): ['a', 'b', 'c', 'd'] => ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c']; "</span>
|
||||
<span class="o">+</span> <span class="s2">"slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also sslice()."</span><span class="p">,</span>
|
||||
<span class="s2">"sslice(start:stop:step)"</span><span class="p">:</span> <span class="s2">"[s(tring) slice] Slice values in a list using same semantics as Python's string slicing, "</span>
|
||||
<span class="o">+</span> <span class="s2">"e.g. sslice(1:3):'abcd => 'bc'; sslice(1:4:2): 'abcd' => 'bd', etc. See also slice()."</span><span class="p">,</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="c1"># Just the substitutions without the braces</span>
|
||||
@@ -578,6 +607,7 @@
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">filepath</span> <span class="o">=</span> <span class="n">options</span><span class="o">.</span><span class="n">filepath</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">quote</span> <span class="o">=</span> <span class="n">options</span><span class="o">.</span><span class="n">quote</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">dest_path</span> <span class="o">=</span> <span class="n">options</span><span class="o">.</span><span class="n">dest_path</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">variables</span> <span class="o">=</span> <span class="p">{}</span>
|
||||
|
||||
<div class="viewcode-block" id="PhotoTemplate.render"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.render">[docs]</a> <span class="k">def</span> <span class="nf">render</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span>
|
||||
@@ -626,14 +656,13 @@
|
||||
<span class="k">def</span> <span class="nf">_render_statement</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span>
|
||||
<span class="n">statement</span><span class="p">,</span>
|
||||
<span class="n">path_sep</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">field_arg</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="p">):</span>
|
||||
<span class="n">path_sep</span> <span class="o">=</span> <span class="n">path_sep</span> <span class="ow">or</span> <span class="bp">self</span><span class="o">.</span><span class="n">path_sep</span>
|
||||
<span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="n">unmatched</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="k">for</span> <span class="n">ts</span> <span class="ow">in</span> <span class="n">statement</span><span class="o">.</span><span class="n">template_strings</span><span class="p">:</span>
|
||||
<span class="n">results</span><span class="p">,</span> <span class="n">unmatched</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_render_template_string</span><span class="p">(</span>
|
||||
<span class="n">ts</span><span class="p">,</span> <span class="n">results</span><span class="o">=</span><span class="n">results</span><span class="p">,</span> <span class="n">unmatched</span><span class="o">=</span><span class="n">unmatched</span><span class="p">,</span> <span class="n">path_sep</span><span class="o">=</span><span class="n">path_sep</span>
|
||||
<span class="n">ts</span><span class="p">,</span> <span class="n">results</span><span class="o">=</span><span class="n">results</span><span class="p">,</span> <span class="n">unmatched</span><span class="o">=</span><span class="n">unmatched</span><span class="p">,</span> <span class="n">field_arg</span><span class="o">=</span><span class="n">field_arg</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">rendered_strings</span> <span class="o">=</span> <span class="n">results</span>
|
||||
@@ -653,7 +682,7 @@
|
||||
<span class="k">def</span> <span class="nf">_render_template_string</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span>
|
||||
<span class="n">ts</span><span class="p">,</span>
|
||||
<span class="n">path_sep</span><span class="p">,</span>
|
||||
<span class="n">field_arg</span><span class="p">,</span>
|
||||
<span class="n">results</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">unmatched</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="p">):</span>
|
||||
@@ -665,11 +694,6 @@
|
||||
<span class="k">if</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="p">:</span>
|
||||
<span class="c1"># have a template field to process</span>
|
||||
<span class="n">field</span> <span class="o">=</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">field</span>
|
||||
<span class="n">field_part</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">0</span><span class="p">]</span>
|
||||
<span class="k">if</span> <span class="n">field</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">FIELD_NAMES</span> <span class="ow">and</span> <span class="n">field_part</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">FIELD_NAMES</span><span class="p">:</span>
|
||||
<span class="n">unmatched</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">field</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="p">[],</span> <span class="n">unmatched</span>
|
||||
|
||||
<span class="n">subfield</span> <span class="o">=</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">subfield</span>
|
||||
|
||||
<span class="c1"># process filters</span>
|
||||
@@ -677,14 +701,15 @@
|
||||
<span class="k">if</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">filter</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">filters</span> <span class="o">=</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">filter</span><span class="o">.</span><span class="n">value</span>
|
||||
|
||||
<span class="c1"># process path_sep</span>
|
||||
<span class="k">if</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">pathsep</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">path_sep</span> <span class="o">=</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">pathsep</span><span class="o">.</span><span class="n">value</span>
|
||||
<span class="c1"># process field arguments</span>
|
||||
<span class="k">if</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">fieldarg</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">field_arg</span> <span class="o">=</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">fieldarg</span><span class="o">.</span><span class="n">value</span>
|
||||
|
||||
<span class="c1"># process delim</span>
|
||||
<span class="k">if</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">delim</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="c1"># if value is None, means format was {+field}</span>
|
||||
<span class="n">delim</span> <span class="o">=</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">delim</span><span class="o">.</span><span class="n">value</span> <span class="ow">or</span> <span class="s2">""</span>
|
||||
<span class="n">delim</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">expand_variables_to_str</span><span class="p">(</span><span class="n">delim</span><span class="p">,</span> <span class="s2">"delim"</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">delim</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
@@ -693,7 +718,7 @@
|
||||
<span class="k">if</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">bool</span><span class="o">.</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="n">bool_val</span><span class="p">,</span> <span class="n">u</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_render_statement</span><span class="p">(</span>
|
||||
<span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">bool</span><span class="o">.</span><span class="n">value</span><span class="p">,</span>
|
||||
<span class="n">path_sep</span><span class="o">=</span><span class="n">path_sep</span><span class="p">,</span>
|
||||
<span class="n">field_arg</span><span class="o">=</span><span class="n">field_arg</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">unmatched</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">u</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
@@ -709,7 +734,7 @@
|
||||
<span class="k">if</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">default</span><span class="o">.</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="n">default</span><span class="p">,</span> <span class="n">u</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_render_statement</span><span class="p">(</span>
|
||||
<span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">default</span><span class="o">.</span><span class="n">value</span><span class="p">,</span>
|
||||
<span class="n">path_sep</span><span class="o">=</span><span class="n">path_sep</span><span class="p">,</span>
|
||||
<span class="n">field_arg</span><span class="o">=</span><span class="n">field_arg</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">unmatched</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">u</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
@@ -724,11 +749,11 @@
|
||||
<span class="n">negation</span> <span class="o">=</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">conditional</span><span class="o">.</span><span class="n">negation</span>
|
||||
<span class="k">if</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">conditional</span><span class="o">.</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="c1"># conditional value is also a TemplateString</span>
|
||||
<span class="n">conditional_value</span><span class="p">,</span> <span class="n">u</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_render_statement</span><span class="p">(</span>
|
||||
<span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">conditional</span><span class="o">.</span><span class="n">value</span><span class="p">,</span>
|
||||
<span class="n">path_sep</span><span class="o">=</span><span class="n">path_sep</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">unmatched</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">u</span><span class="p">)</span>
|
||||
<span class="n">conditional_value</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="k">for</span> <span class="n">cv</span> <span class="ow">in</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">conditional</span><span class="o">.</span><span class="n">value</span><span class="p">:</span>
|
||||
<span class="n">value</span><span class="p">,</span> <span class="n">u</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_render_statement</span><span class="p">(</span><span class="n">cv</span><span class="p">)</span>
|
||||
<span class="n">conditional_value</span> <span class="o">+=</span> <span class="n">value</span>
|
||||
<span class="n">unmatched</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">u</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="c1"># this shouldn't happen</span>
|
||||
<span class="n">conditional_value</span> <span class="o">=</span> <span class="p">[</span><span class="s2">""</span><span class="p">]</span>
|
||||
@@ -737,43 +762,23 @@
|
||||
<span class="n">negation</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="n">conditional_value</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="k">if</span> <span class="p">(</span>
|
||||
<span class="n">field</span> <span class="ow">in</span> <span class="n">SINGLE_VALUE_SUBSTITUTIONS</span>
|
||||
<span class="ow">or</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">0</span><span class="p">]</span> <span class="ow">in</span> <span class="n">SINGLE_VALUE_SUBSTITUTIONS</span>
|
||||
<span class="p">):</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value</span><span class="p">(</span>
|
||||
<span class="n">field</span><span class="p">,</span>
|
||||
<span class="n">default</span><span class="o">=</span><span class="n">default</span><span class="p">,</span>
|
||||
<span class="n">subfield</span><span class="o">=</span><span class="n">subfield</span><span class="p">,</span>
|
||||
<span class="c1"># delim=delim or self.inplace_sep,</span>
|
||||
<span class="c1"># path_sep=path_sep,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"exiftool"</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">subfield</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">"SyntaxError: GROUP:NAME subfield must not be null with {exiftool:GROUP:NAME}'"</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">"%"</span><span class="p">):</span>
|
||||
<span class="c1"># variable in form {%var}</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">variables</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">field</span><span class="p">[</span><span class="mi">1</span><span class="p">:],</span> <span class="kc">None</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">vals</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Variable '</span><span class="si">{</span><span class="n">field</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span><span class="si">}</span><span class="s2">' is not defined."</span><span class="p">)</span>
|
||||
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"var"</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">subfield</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">default</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span>
|
||||
<span class="s2">"var must have a subfield and value in form {var:subfield,value}"</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_exiftool</span><span class="p">(</span>
|
||||
<span class="n">subfield</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"function"</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">subfield</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">"SyntaxError: filename and function must not be null with {function::filename.py:function_name}"</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_function</span><span class="p">(</span>
|
||||
<span class="n">subfield</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">elif</span> <span class="n">field</span> <span class="ow">in</span> <span class="n">MULTI_VALUE_SUBSTITUTIONS</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">"photo"</span><span class="p">):</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_multi</span><span class="p">(</span>
|
||||
<span class="n">field</span><span class="p">,</span> <span class="n">subfield</span><span class="p">,</span> <span class="n">path_sep</span><span class="o">=</span><span class="n">path_sep</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">default</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">elif</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">0</span><span class="p">]</span> <span class="ow">in</span> <span class="n">PATHLIB_SUBSTITUTIONS</span><span class="p">:</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_pathlib</span><span class="p">(</span><span class="n">field</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">variables</span><span class="p">[</span><span class="n">subfield</span><span class="p">]</span> <span class="o">=</span> <span class="n">default</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">unmatched</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">field</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="p">[],</span> <span class="n">unmatched</span>
|
||||
<span class="n">vals</span><span class="p">,</span> <span class="n">u</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_field_values</span><span class="p">(</span><span class="n">field</span><span class="p">,</span> <span class="n">subfield</span><span class="p">,</span> <span class="n">field_arg</span><span class="p">,</span> <span class="n">default</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">u</span><span class="p">:</span>
|
||||
<span class="n">unmatched</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">u</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="p">[],</span> <span class="n">unmatched</span>
|
||||
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="p">[</span><span class="n">val</span> <span class="k">for</span> <span class="n">val</span> <span class="ow">in</span> <span class="n">vals</span> <span class="k">if</span> <span class="n">val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">]</span>
|
||||
|
||||
@@ -782,7 +787,7 @@
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="p">[</span><span class="n">sep</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">sorted</span><span class="p">(</span><span class="n">vals</span><span class="p">))]</span> <span class="k">if</span> <span class="n">vals</span> <span class="k">else</span> <span class="p">[]</span>
|
||||
|
||||
<span class="k">for</span> <span class="n">filter_</span> <span class="ow">in</span> <span class="n">filters</span><span class="p">:</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_filter</span><span class="p">(</span><span class="n">filter_</span><span class="p">,</span> <span class="n">vals</span><span class="p">)</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_filter_values</span><span class="p">(</span><span class="n">filter_</span><span class="p">,</span> <span class="n">vals</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># process find/replace</span>
|
||||
<span class="k">if</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">findreplace</span><span class="p">:</span>
|
||||
@@ -790,7 +795,9 @@
|
||||
<span class="k">for</span> <span class="n">val</span> <span class="ow">in</span> <span class="n">vals</span><span class="p">:</span>
|
||||
<span class="k">for</span> <span class="n">pair</span> <span class="ow">in</span> <span class="n">ts</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">findreplace</span><span class="o">.</span><span class="n">pairs</span><span class="p">:</span>
|
||||
<span class="n">find</span> <span class="o">=</span> <span class="n">pair</span><span class="o">.</span><span class="n">find</span> <span class="ow">or</span> <span class="s2">""</span>
|
||||
<span class="n">find</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">expand_variables_to_str</span><span class="p">(</span><span class="n">find</span><span class="p">,</span> <span class="s2">"find/replace"</span><span class="p">)</span>
|
||||
<span class="n">repl</span> <span class="o">=</span> <span class="n">pair</span><span class="o">.</span><span class="n">replace</span> <span class="ow">or</span> <span class="s2">""</span>
|
||||
<span class="n">repl</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">expand_variables_to_str</span><span class="p">(</span><span class="n">repl</span><span class="p">,</span> <span class="s2">"find/replace"</span><span class="p">)</span>
|
||||
<span class="n">val</span> <span class="o">=</span> <span class="n">val</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="n">find</span><span class="p">,</span> <span class="n">repl</span><span class="p">)</span>
|
||||
<span class="n">new_vals</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">val</span><span class="p">)</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="n">new_vals</span>
|
||||
@@ -817,22 +824,23 @@
|
||||
|
||||
<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>
|
||||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">vals</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">1</span> <span class="ow">or</span> <span class="nb">len</span><span class="p">(</span><span class="n">conditional_value</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
|
||||
<span class="sa">f</span><span class="s2">"comparison operators may only be used with a single value: </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="c1"># returns True if any of the values match the condition</span>
|
||||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">conditional_value</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span>
|
||||
<span class="sa">f</span><span class="s2">"comparison operators may only be used with a single conditional value: </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="k">try</span><span class="p">:</span>
|
||||
<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="n">match</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</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">v</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="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">vals</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="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span>
|
||||
<span class="sa">f</span><span class="s2">"comparison operators may only be used with values that can be converted to numbers: </span><span class="si">{</span><span class="n">vals</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">conditional_value</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
|
||||
|
||||
@@ -874,7 +882,8 @@
|
||||
|
||||
<span class="k">if</span> <span class="n">is_bool</span><span class="p">:</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="n">default</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">vals</span> <span class="k">else</span> <span class="n">bool_val</span>
|
||||
<span class="k">elif</span> <span class="ow">not</span> <span class="n">vals</span><span class="p">:</span>
|
||||
<span class="k">elif</span> <span class="ow">not</span> <span class="n">vals</span> <span class="ow">and</span> <span class="n">field</span> <span class="o">!=</span> <span class="s2">"var"</span><span class="p">:</span>
|
||||
<span class="c1"># don't assign default value if the template was variable assignment</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="n">default</span> <span class="ow">or</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">none_str</span><span class="p">]</span>
|
||||
|
||||
<span class="n">pre</span> <span class="o">=</span> <span class="n">ts</span><span class="o">.</span><span class="n">pre</span> <span class="ow">or</span> <span class="s2">""</span>
|
||||
@@ -896,14 +905,103 @@
|
||||
|
||||
<span class="k">return</span> <span class="n">results</span><span class="p">,</span> <span class="n">unmatched</span>
|
||||
|
||||
<div class="viewcode-block" id="PhotoTemplate.expand_variables_to_str"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.expand_variables_to_str">[docs]</a> <span class="k">def</span> <span class="nf">expand_variables_to_str</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span>
|
||||
<span class="sd">"""</span>
|
||||
<span class="sd"> Expand variables in value and return a str of the expanded value.</span>
|
||||
<span class="sd"> Enforce that the expanded value is a single value, raises ValueError if not.</span>
|
||||
|
||||
<span class="sd"> Args:</span>
|
||||
<span class="sd"> value: the value to expand</span>
|
||||
<span class="sd"> name: the name of the value being expanded (used in error messages)</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="n">expanded</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">expand_variables</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">expanded</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2"> must have a single value, not </span><span class="si">{</span><span class="n">expanded</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">expanded</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span></div>
|
||||
|
||||
<div class="viewcode-block" id="PhotoTemplate.expand_variables"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.expand_variables">[docs]</a> <span class="k">def</span> <span class="nf">expand_variables</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
|
||||
<span class="sd">"""Expand variables in value"""</span>
|
||||
<span class="c1"># replace any variables with their values</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="p">[</span><span class="n">value</span><span class="p">]</span>
|
||||
<span class="n">new_values</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="c1"># allow %% to escape %, match variables in form %var</span>
|
||||
<span class="n">variable_match</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">"(?:</span><span class="si">%%</span><span class="s2">)*(%[\w]+)?"</span><span class="p">)</span>
|
||||
<span class="k">while</span> <span class="kc">True</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="n">match</span> <span class="o">=</span> <span class="n">variable_match</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">match</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">match</span><span class="p">[</span><span class="mi">1</span><span class="p">]:</span>
|
||||
<span class="k">break</span>
|
||||
<span class="n">var</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
|
||||
<span class="n">var_name</span> <span class="o">=</span> <span class="n">var</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
|
||||
<span class="k">if</span> <span class="n">var_name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">variables</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Variable '</span><span class="si">{</span><span class="n">var_name</span><span class="si">}</span><span class="s2">' is not defined."</span><span class="p">)</span>
|
||||
<span class="k">for</span> <span class="n">val</span> <span class="ow">in</span> <span class="n">values</span><span class="p">:</span>
|
||||
<span class="k">for</span> <span class="n">var_val</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">variables</span><span class="p">[</span><span class="n">var_name</span><span class="p">]:</span>
|
||||
<span class="n">new_values</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
|
||||
<span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sa">f</span><span class="s2">"(%%)*</span><span class="si">{</span><span class="n">var</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="sa">r</span><span class="s2">"\g<1>"</span> <span class="o">+</span> <span class="n">var_val</span><span class="p">,</span> <span class="n">val</span><span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">new_values</span> <span class="o">==</span> <span class="n">values</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">new_values</span><span class="p">:</span>
|
||||
<span class="k">break</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="n">new_values</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
|
||||
<span class="n">new_values</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
|
||||
<span class="c1"># replace %% with %</span>
|
||||
<span class="c1"># any %% left in the string will be replaced with %</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="p">[</span><span class="n">value</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"</span><span class="si">%%</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"%"</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">return</span> <span class="n">values</span></div>
|
||||
|
||||
<div class="viewcode-block" id="PhotoTemplate.get_field_values"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_field_values">[docs]</a> <span class="k">def</span> <span class="nf">get_field_values</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span>
|
||||
<span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
|
||||
<span class="n">subfield</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
|
||||
<span class="n">field_arg</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
|
||||
<span class="n">default</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
|
||||
<span class="p">)</span> <span class="o">-></span> <span class="n">Tuple</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]]:</span>
|
||||
<span class="sd">"""Get the values for a field"""</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="n">unmatched</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="k">if</span> <span class="p">(</span>
|
||||
<span class="n">field</span> <span class="ow">in</span> <span class="n">SINGLE_VALUE_SUBSTITUTIONS</span>
|
||||
<span class="ow">or</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">0</span><span class="p">]</span> <span class="ow">in</span> <span class="n">SINGLE_VALUE_SUBSTITUTIONS</span>
|
||||
<span class="p">):</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value</span><span class="p">(</span>
|
||||
<span class="n">field</span><span class="p">,</span>
|
||||
<span class="n">default</span><span class="o">=</span><span class="n">default</span><span class="p">,</span>
|
||||
<span class="n">subfield</span><span class="o">=</span><span class="n">subfield</span><span class="p">,</span>
|
||||
<span class="n">field_arg</span><span class="o">=</span><span class="n">field_arg</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"exiftool"</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">subfield</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">"SyntaxError: GROUP:NAME subfield must not be null with {exiftool:GROUP:NAME}'"</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_exiftool</span><span class="p">(</span>
|
||||
<span class="n">subfield</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"function"</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">subfield</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">"SyntaxError: filename and function must not be null with {function::filename.py:function_name}"</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_function</span><span class="p">(</span><span class="n">subfield</span><span class="p">,</span> <span class="n">field_arg</span><span class="p">)</span>
|
||||
<span class="k">elif</span> <span class="n">field</span> <span class="ow">in</span> <span class="n">MULTI_VALUE_SUBSTITUTIONS</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">"photo"</span><span class="p">):</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_multi</span><span class="p">(</span>
|
||||
<span class="n">field</span><span class="p">,</span> <span class="n">subfield</span><span class="p">,</span> <span class="n">path_sep</span><span class="o">=</span><span class="n">field_arg</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">default</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">elif</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">0</span><span class="p">]</span> <span class="ow">in</span> <span class="n">PATHLIB_SUBSTITUTIONS</span><span class="p">:</span>
|
||||
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_pathlib</span><span class="p">(</span><span class="n">field</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">unmatched</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">field</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="p">[],</span> <span class="n">unmatched</span>
|
||||
<span class="k">return</span> <span class="n">vals</span><span class="p">,</span> <span class="n">unmatched</span></div>
|
||||
|
||||
<div class="viewcode-block" id="PhotoTemplate.get_template_value"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_template_value">[docs]</a> <span class="k">def</span> <span class="nf">get_template_value</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span>
|
||||
<span class="n">field</span><span class="p">,</span>
|
||||
<span class="n">default</span><span class="p">,</span>
|
||||
<span class="n">subfield</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="c1"># bool_val=None,</span>
|
||||
<span class="c1"># delim=None,</span>
|
||||
<span class="c1"># path_sep=None,</span>
|
||||
<span class="n">subfield</span><span class="p">,</span>
|
||||
<span class="n">field_arg</span><span class="p">,</span>
|
||||
<span class="p">):</span>
|
||||
<span class="sd">"""lookup value for template field (single-value template substitutions)</span>
|
||||
|
||||
@@ -1187,6 +1285,8 @@
|
||||
<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">moment_info</span><span class="o">.</span><span class="n">title</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">moment_info</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">"uuid"</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">uuid</span>
|
||||
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"shortuuid"</span><span class="p">:</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="n">uuid_to_shortuuid</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span><span class="p">)</span> <span class="k">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="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">"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>
|
||||
@@ -1200,9 +1300,8 @@
|
||||
<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">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="n">start_id</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">field_arg</span><span class="p">)</span> <span class="k">if</span> <span class="n">field_arg</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="mi">0</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="n">start_id</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">else</span><span class="p">:</span>
|
||||
<span class="c1"># if here, didn't get a match</span>
|
||||
@@ -1250,57 +1349,124 @@
|
||||
|
||||
<span class="k">return</span> <span class="p">[</span><span class="n">value</span><span class="p">]</span></div>
|
||||
|
||||
<span class="k">def</span> <span class="nf">get_template_value_filter</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filter_</span><span class="p">,</span> <span class="n">values</span><span class="p">):</span>
|
||||
<div class="viewcode-block" id="PhotoTemplate.get_filter_values"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_filter_values">[docs]</a> <span class="k">def</span> <span class="nf">get_filter_values</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filter_</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">values</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-></span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
|
||||
<span class="sd">"""Return filtered values"""</span>
|
||||
|
||||
<span class="c1"># extract args, if any</span>
|
||||
<span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="sa">r</span><span class="s2">"\(.*\)"</span><span class="p">,</span> <span class="n">filter_</span><span class="p">):</span>
|
||||
<span class="n">filter_</span><span class="p">,</span> <span class="n">args</span> <span class="o">=</span> <span class="n">filter_</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">args</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">rstrip</span><span class="p">(</span><span class="s2">")"</span><span class="p">)</span>
|
||||
<span class="n">args</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">expand_variables_to_str</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="s2">"Filter arguments"</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">args</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># check that filter name (without subfields or arguments) is valid</span>
|
||||
<span class="n">valid_filters</span> <span class="o">=</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="s2">"("</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">FILTER_VALUES</span><span class="p">]</span>
|
||||
<span class="k">if</span> <span class="n">filter_</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">0</span><span class="p">]</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">valid_filters</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Unknown filter: </span><span class="si">{</span><span class="n">filter_</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">filter_</span> <span class="ow">in</span> <span class="p">[</span>
|
||||
<span class="s2">"split"</span><span class="p">,</span>
|
||||
<span class="s2">"chop"</span><span class="p">,</span>
|
||||
<span class="s2">"chomp"</span><span class="p">,</span>
|
||||
<span class="s2">"append"</span><span class="p">,</span>
|
||||
<span class="s2">"prepend"</span><span class="p">,</span>
|
||||
<span class="s2">"remove"</span><span class="p">,</span>
|
||||
<span class="s2">"slice"</span><span class="p">,</span>
|
||||
<span class="s2">"sslice"</span><span class="p">,</span>
|
||||
<span class="p">]</span> <span class="ow">and</span> <span class="p">(</span><span class="n">args</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">or</span> <span class="ow">not</span> <span class="nb">len</span><span class="p">(</span><span class="n">args</span><span class="p">)):</span>
|
||||
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">filter_</span><span class="si">}</span><span class="s2"> requires arguments"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"lower"</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">v</span><span class="o">.</span><span class="n">lower</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>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">values</span><span class="o">.</span><span class="n">lower</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="n">v</span><span class="o">.</span><span class="n">lower</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>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"upper"</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">v</span><span class="o">.</span><span class="n">upper</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>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">values</span><span class="o">.</span><span class="n">upper</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="n">v</span><span class="o">.</span><span class="n">upper</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>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"strip"</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">v</span><span class="o">.</span><span class="n">strip</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>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">values</span><span class="o">.</span><span class="n">strip</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="n">v</span><span class="o">.</span><span class="n">strip</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>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"capitalize"</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">v</span><span class="o">.</span><span class="n">capitalize</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>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">values</span><span class="o">.</span><span class="n">capitalize</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="n">v</span><span class="o">.</span><span class="n">capitalize</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>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"titlecase"</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">v</span><span class="o">.</span><span class="n">title</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>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">values</span><span class="o">.</span><span class="n">title</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="n">v</span><span class="o">.</span><span class="n">title</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>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"braces"</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="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="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="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="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="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="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="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="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="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="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="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>
|
||||
<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>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"split"</span><span class="p">:</span>
|
||||
<span class="c1"># split on delimiter</span>
|
||||
<span class="n">delim</span> <span class="o">=</span> <span class="n">args</span>
|
||||
<span class="k">if</span> <span class="n">delim</span><span class="p">:</span>
|
||||
<span class="n">new_values</span> <span class="o">=</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>
|
||||
<span class="n">new_values</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">v</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">delim</span><span class="p">))</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="n">new_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="n">shlex</span><span class="o">.</span><span class="n">quote</span><span class="p">(</span><span class="n">values</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="n">values</span>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"chop"</span><span class="p">:</span>
|
||||
<span class="c1"># chop off characters from the end</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">chop</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
|
||||
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Invalid value for chop: </span><span class="si">{</span><span class="n">args</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span><span class="p">[:</span><span class="o">-</span><span class="n">chop</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> <span class="k">if</span> <span class="n">chop</span> <span class="k">else</span> <span class="n">values</span>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"chomp"</span><span class="p">:</span>
|
||||
<span class="c1"># chop off characters from the beginning</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">chomp</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
|
||||
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Invalid value for chomp: </span><span class="si">{</span><span class="n">args</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span><span class="p">[</span><span class="n">chomp</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> <span class="k">if</span> <span class="n">chomp</span> <span class="k">else</span> <span class="n">values</span>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"autosplit"</span><span class="p">:</span>
|
||||
<span class="c1"># try to split keyword strings automatically</span>
|
||||
<span class="n">temp_values</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</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="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">temp_values</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</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="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">temp_values</span><span class="p">]</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="k">for</span> <span class="n">val</span> <span class="ow">in</span> <span class="n">temp_values</span><span class="p">:</span>
|
||||
<span class="n">value</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">val</span><span class="o">.</span><span class="n">split</span><span class="p">())</span>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"sort"</span><span class="p">:</span>
|
||||
<span class="c1"># sort list of values</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"rsort"</span><span class="p">:</span>
|
||||
<span class="c1"># reverse sort list of values</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">values</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="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"reverse"</span><span class="p">:</span>
|
||||
<span class="c1"># reverse list of values</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="n">values</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"uniq"</span><span class="p">:</span>
|
||||
<span class="c1"># remove duplicate values from list</span>
|
||||
<span class="n">temp_values</span> <span class="o">=</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>
|
||||
<span class="k">if</span> <span class="n">v</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">temp_values</span><span class="p">:</span>
|
||||
<span class="n">temp_values</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">v</span><span class="p">)</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="n">temp_values</span>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"join"</span><span class="p">:</span>
|
||||
<span class="c1"># join list of values with delimiter</span>
|
||||
<span class="n">delim</span> <span class="o">=</span> <span class="n">args</span> <span class="ow">or</span> <span class="s2">""</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">delim</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">values</span><span class="p">)]</span>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"append"</span><span class="p">:</span>
|
||||
<span class="c1"># append value to list</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="n">values</span> <span class="o">+</span> <span class="p">[</span><span class="n">args</span><span class="p">]</span>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"prepend"</span><span class="p">:</span>
|
||||
<span class="c1"># prepend value to list</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">args</span><span class="p">]</span> <span class="o">+</span> <span class="n">values</span>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"remove"</span><span class="p">:</span>
|
||||
<span class="c1"># remove value from list</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span> <span class="k">if</span> <span class="n">v</span> <span class="o">!=</span> <span class="n">args</span><span class="p">]</span>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"slice"</span><span class="p">:</span>
|
||||
<span class="c1"># slice list of values</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="n">values</span><span class="p">[</span><span class="n">create_slice</span><span class="p">(</span><span class="n">args</span><span class="p">)]</span>
|
||||
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"sslice"</span><span class="p">:</span>
|
||||
<span class="c1"># slice each value in a list</span>
|
||||
<span class="n">slice_</span> <span class="o">=</span> <span class="n">create_slice</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
|
||||
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span><span class="p">[</span><span class="n">slice_</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>
|
||||
<span class="k">elif</span> <span class="n">filter_</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"function:"</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">get_template_value_filter_function</span><span class="p">(</span><span class="n">filter_</span><span class="p">,</span> <span class="n">values</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">get_template_value_filter_function</span><span class="p">(</span><span class="n">filter_</span><span class="p">,</span> <span class="n">args</span><span class="p">,</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="k">return</span> <span class="n">value</span>
|
||||
<span class="k">return</span> <span class="n">value</span></div>
|
||||
|
||||
<div class="viewcode-block" id="PhotoTemplate.get_template_value_multi"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_template_value_multi">[docs]</a> <span class="k">def</span> <span class="nf">get_template_value_multi</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">,</span> <span class="n">subfield</span><span class="p">,</span> <span class="n">path_sep</span><span class="p">,</span> <span class="n">default</span><span class="p">):</span>
|
||||
<span class="sd">"""lookup value for template field (multi-value template substitutions)</span>
|
||||
@@ -1320,6 +1486,8 @@
|
||||
|
||||
<span class="sd">""" return list of values for a multi-valued template field """</span>
|
||||
|
||||
<span class="n">path_sep</span> <span class="o">=</span> <span class="n">path_sep</span> <span class="ow">or</span> <span class="bp">self</span><span class="o">.</span><span class="n">path_sep</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>
|
||||
|
||||
@@ -1385,6 +1553,8 @@
|
||||
<span class="n">values</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">default</span> <span class="k">if</span> <span class="n">v</span><span class="p">]</span>
|
||||
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"strip"</span><span class="p">:</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">default</span><span class="p">]</span>
|
||||
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"format"</span><span class="p">:</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_format_values</span><span class="p">(</span><span class="n">field</span><span class="p">,</span> <span class="n">subfield</span><span class="p">,</span> <span class="n">default</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">"photo"</span><span class="p">):</span>
|
||||
<span class="c1"># provide access to PhotoInfo object</span>
|
||||
<span class="n">properties</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>
|
||||
@@ -1399,10 +1569,11 @@
|
||||
<span class="n">obj</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">property_</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">obj</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="k">break</span>
|
||||
<span class="k">except</span> <span class="ne">AttributeError</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="s2">"Invalid property for </span><span class="si">{photo}</span><span class="s2"> template: "</span> <span class="o">+</span> <span class="sa">f</span><span class="s2">"'</span><span class="si">{</span><span class="n">property_</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">obj</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="nb">bool</span><span class="p">):</span>
|
||||
@@ -1427,6 +1598,31 @@
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="n">values</span> <span class="ow">or</span> <span class="p">[]</span>
|
||||
<span class="k">return</span> <span class="n">values</span></div>
|
||||
|
||||
<div class="viewcode-block" id="PhotoTemplate.get_format_values"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_format_values">[docs]</a> <span class="k">def</span> <span class="nf">get_format_values</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">subfield</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">default</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>
|
||||
<span class="p">)</span> <span class="o">-></span> <span class="n">Optional</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]]]:</span>
|
||||
<span class="sd">"""Return values for {format} templates"""</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">field</span> <span class="o">!=</span> <span class="s2">"format"</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">"Unhandled template value in get_format_values: </span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">subfield</span> <span class="ow">or</span> <span class="s2">":"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">subfield</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="s2">"</span><span class="si">{format}</span><span class="s2"> requires subfield in form TYPE:FORMAT"</span><span class="p">)</span>
|
||||
<span class="n">type_</span><span class="p">,</span> <span class="n">format_str</span> <span class="o">=</span> <span class="n">subfield</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="k">if</span> <span class="n">type_</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">(</span><span class="s2">"int"</span><span class="p">,</span> <span class="s2">"float"</span><span class="p">,</span> <span class="s2">"str"</span><span class="p">):</span>
|
||||
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span>
|
||||
<span class="sa">f</span><span class="s2">"'</span><span class="si">{</span><span class="n">type_</span><span class="si">}</span><span class="s2">' is not a valid type for </span><span class="si">{</span><span class="nb">format</span><span class="si">}</span><span class="s2">: must be one of 'int', 'float', 'str'"</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">type_</span> <span class="o">==</span> <span class="s2">"int"</span><span class="p">:</span>
|
||||
<span class="c1"># convert to float then int to avoid error when converting a string float to int</span>
|
||||
<span class="n">default_</span> <span class="o">=</span> <span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="nb">float</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">default</span><span class="p">]</span>
|
||||
<span class="k">elif</span> <span class="n">type_</span> <span class="o">==</span> <span class="s2">"float"</span><span class="p">:</span>
|
||||
<span class="n">default_</span> <span class="o">=</span> <span class="p">[</span><span class="nb">float</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">default</span><span class="p">]</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">default_</span> <span class="o">=</span> <span class="n">default</span>
|
||||
<span class="n">format_str</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">expand_variables_to_str</span><span class="p">(</span><span class="n">format_str</span><span class="p">,</span> <span class="s2">"format string"</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="p">[</span><span class="n">format_str_value</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">format_str</span><span class="p">)</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">default_</span><span class="p">]</span></div>
|
||||
|
||||
<div class="viewcode-block" id="PhotoTemplate.get_template_value_exiftool"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_template_value_exiftool">[docs]</a> <span class="k">def</span> <span class="nf">get_template_value_exiftool</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span>
|
||||
<span class="n">subfield</span><span class="p">,</span>
|
||||
@@ -1460,6 +1656,7 @@
|
||||
<div class="viewcode-block" id="PhotoTemplate.get_template_value_function"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_template_value_function">[docs]</a> <span class="k">def</span> <span class="nf">get_template_value_function</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span>
|
||||
<span class="n">subfield</span><span class="p">,</span>
|
||||
<span class="n">field_arg</span><span class="p">,</span>
|
||||
<span class="p">):</span>
|
||||
<span class="sd">"""Get template value from external function"""</span>
|
||||
|
||||
@@ -1475,7 +1672,13 @@
|
||||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"'</span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">' does not appear to be a file"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">template_func</span> <span class="o">=</span> <span class="n">load_function</span><span class="p">(</span><span class="n">filename_validated</span><span class="p">,</span> <span class="n">funcname</span><span class="p">)</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="n">template_func</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">options</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">options</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">uuid</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="c1"># must be a PhotoInfoNone instance</span>
|
||||
<span class="c1"># if no uuid, then template is being validated but not actually run</span>
|
||||
<span class="c1"># so don't run the function</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="n">template_func</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">options</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="n">field_arg</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">values</span><span class="p">,</span> <span class="p">(</span><span class="nb">str</span><span class="p">,</span> <span class="nb">list</span><span class="p">)):</span>
|
||||
<span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span>
|
||||
@@ -1493,8 +1696,9 @@
|
||||
|
||||
<span class="k">return</span> <span class="n">values</span></div>
|
||||
|
||||
<div class="viewcode-block" id="PhotoTemplate.get_template_value_filter_function"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_template_value_filter_function">[docs]</a> <span class="k">def</span> <span class="nf">get_template_value_filter_function</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filter_</span><span class="p">,</span> <span class="n">values</span><span class="p">):</span>
|
||||
<div class="viewcode-block" id="PhotoTemplate.get_template_value_filter_function"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_template_value_filter_function">[docs]</a> <span class="k">def</span> <span class="nf">get_template_value_filter_function</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filter_</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">values</span><span class="p">):</span>
|
||||
<span class="sd">"""Filter template value from external function"""</span>
|
||||
<span class="c1"># TODO: add args to filter function call? Would change signature of function</span>
|
||||
|
||||
<span class="n">filter_</span> <span class="o">=</span> <span class="n">filter_</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"function:"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span>
|
||||
|
||||
@@ -1513,7 +1717,11 @@
|
||||
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">values</span><span class="p">,</span> <span class="p">(</span><span class="nb">list</span><span class="p">,</span> <span class="nb">tuple</span><span class="p">)):</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="p">[</span><span class="n">values</span><span class="p">]</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="n">template_func</span><span class="p">(</span><span class="n">values</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">uuid</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="c1"># if uuid is None, it's a PhotoInfoNone instance and template is being validated</span>
|
||||
<span class="c1"># so don't run the function</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="n">template_func</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">values</span><span class="p">,</span> <span class="nb">list</span><span class="p">):</span>
|
||||
<span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span>
|
||||
@@ -1525,10 +1733,7 @@
|
||||
<div class="viewcode-block" id="PhotoTemplate.get_photo_video_type"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_photo_video_type">[docs]</a> <span class="k">def</span> <span class="nf">get_photo_video_type</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">default</span><span class="p">):</span>
|
||||
<span class="sd">"""return media type, e.g. photo or video"""</span>
|
||||
<span class="n">default_dict</span> <span class="o">=</span> <span class="n">parse_default_kv</span><span class="p">(</span><span class="n">default</span><span class="p">,</span> <span class="n">PHOTO_VIDEO_TYPE_DEFAULTS</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">isphoto</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">default_dict</span><span class="p">[</span><span class="s2">"photo"</span><span class="p">]</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">default_dict</span><span class="p">[</span><span class="s2">"video"</span><span class="p">]</span></div>
|
||||
<span class="k">return</span> <span class="n">default_dict</span><span class="p">[</span><span class="s2">"photo"</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">isphoto</span> <span class="k">else</span> <span class="n">default_dict</span><span class="p">[</span><span class="s2">"video"</span><span class="p">]</span></div>
|
||||
|
||||
<div class="viewcode-block" id="PhotoTemplate.get_media_type"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_media_type">[docs]</a> <span class="k">def</span> <span class="nf">get_media_type</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">default</span><span class="p">):</span>
|
||||
<span class="sd">"""return special media type, e.g. slow_mo, panorama, etc., defaults to photo or video if no special type"""</span>
|
||||
@@ -1555,13 +1760,9 @@
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">default_dict</span><span class="p">[</span><span class="s2">"photo"</span><span class="p">]</span></div>
|
||||
|
||||
<span class="k">def</span> <span class="nf">get_photo_bool_attribute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">attr</span><span class="p">,</span> <span class="n">default</span><span class="p">,</span> <span class="n">bool_val</span><span class="p">):</span>
|
||||
<span class="c1"># get value for a PhotoInfo bool attribute</span>
|
||||
<span class="n">val</span> <span class="o">=</span> <span class="nb">getattr</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">attr</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">val</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">bool_val</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">default</span></div>
|
||||
<div class="viewcode-block" id="PhotoTemplate.get_photo_bool_attribute"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_photo_bool_attribute">[docs]</a> <span class="k">def</span> <span class="nf">get_photo_bool_attribute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">attr</span><span class="p">,</span> <span class="n">default</span><span class="p">,</span> <span class="n">bool_val</span><span class="p">):</span>
|
||||
<span class="sd">"""Return the boolean value for a photo attribute"""</span>
|
||||
<span class="k">return</span> <span class="n">bool_val</span> <span class="k">if</span> <span class="p">(</span><span class="n">val</span> <span class="o">:=</span> <span class="nb">getattr</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">attr</span><span class="p">))</span> <span class="k">else</span> <span class="n">default</span></div></div>
|
||||
|
||||
|
||||
<span class="k">def</span> <span class="nf">parse_default_kv</span><span class="p">(</span><span class="n">default</span><span class="p">,</span> <span class="n">default_dict</span><span class="p">):</span>
|
||||
@@ -1685,6 +1886,28 @@
|
||||
<span class="c1"># so the first time this gets called is slow but repeated accesses are fast</span>
|
||||
<span class="n">detected_text</span> <span class="o">=</span> <span class="n">photo</span><span class="o">.</span><span class="n">_detected_text</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="p">[</span><span class="n">text</span> <span class="k">for</span> <span class="n">text</span><span class="p">,</span> <span class="n">conf</span> <span class="ow">in</span> <span class="n">detected_text</span> <span class="k">if</span> <span class="n">conf</span> <span class="o">>=</span> <span class="n">confidence</span><span class="p">]</span>
|
||||
|
||||
|
||||
<span class="k">def</span> <span class="nf">create_slice</span><span class="p">(</span><span class="n">args</span><span class="p">):</span>
|
||||
<span class="sd">"""Create a slice object from a string of args in form "start:end:step" """</span>
|
||||
<span class="n">slice_args</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">":"</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">slice_args</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
|
||||
<span class="n">start</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">slice_args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">or</span> <span class="mi">0</span><span class="p">)</span>
|
||||
<span class="n">end</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="n">step</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="k">elif</span> <span class="nb">len</span><span class="p">(</span><span class="n">slice_args</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
|
||||
<span class="n">start</span><span class="p">,</span> <span class="n">end</span> <span class="o">=</span> <span class="n">slice_args</span>
|
||||
<span class="n">start</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">start</span><span class="p">)</span> <span class="k">if</span> <span class="n">start</span> <span class="o">!=</span> <span class="s2">""</span> <span class="k">else</span> <span class="kc">None</span>
|
||||
<span class="n">end</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">end</span><span class="p">)</span> <span class="k">if</span> <span class="n">end</span> <span class="o">!=</span> <span class="s2">""</span> <span class="k">else</span> <span class="kc">None</span>
|
||||
<span class="n">step</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="k">elif</span> <span class="nb">len</span><span class="p">(</span><span class="n">slice_args</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
|
||||
<span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">,</span> <span class="n">step</span> <span class="o">=</span> <span class="n">slice_args</span>
|
||||
<span class="n">start</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">start</span><span class="p">)</span> <span class="k">if</span> <span class="n">start</span> <span class="o">!=</span> <span class="s2">""</span> <span class="k">else</span> <span class="kc">None</span>
|
||||
<span class="n">end</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">end</span><span class="p">)</span> <span class="k">if</span> <span class="n">end</span> <span class="o">!=</span> <span class="s2">""</span> <span class="k">else</span> <span class="kc">None</span>
|
||||
<span class="n">step</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">step</span><span class="p">)</span> <span class="k">if</span> <span class="n">step</span> <span class="o">!=</span> <span class="s2">""</span> <span class="k">else</span> <span class="kc">None</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Invalid slice: </span><span class="si">{</span><span class="n">args</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="nb">slice</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">,</span> <span class="n">step</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ In its simplest form, a template statement has the form: ``"{template_field}"``\
|
||||
|
||||
Template statements may contain one or more modifiers. The full syntax is:
|
||||
|
||||
``"pretext{delim+template_field:subfield|filter(path_sep)[find,replace] conditional?bool_value,default}posttext"``
|
||||
``"pretext{delim+template_field:subfield(field_arg)|filter[find,replace] conditional?bool_value,default}posttext"``
|
||||
|
||||
Template statements are white-space sensitive meaning that white space (spaces, tabs) changes the meaning of the template statement.
|
||||
|
||||
@@ -30,6 +30,8 @@ e.g. if Photo keywords are ``["foo","bar"]``\ :
|
||||
|
||||
`:subfield`: Some templates have sub-fields, For example, `{exiftool:IPTC:Make}\ ``; the template_field is``\ exiftool\ ``and the sub-field is``\ IPTC:Make`.
|
||||
|
||||
``(field_arg)``\ : optional arguments to pass to the field; for example, with ``{folder_album}`` this is used to pass the path separator used for joining folders and albums when rendering the field (default is "/" for ``{folder_album}``\ ).
|
||||
|
||||
`|filter`: You may optionally append one or more filter commands to the end of the template field using the vertical pipe ('|') symbol. Filters may be combined, separated by '|' as in: ``{keyword|capitalize|parens}``.
|
||||
|
||||
Valid filters are:
|
||||
@@ -45,6 +47,20 @@ Valid filters are:
|
||||
* ``brackets``\ : Enclose value in brackets, e.g. 'value' => '[value]'
|
||||
* ``shell_quote``\ : Quotes the value for safe usage in the shell, e.g. My file.jpeg => 'My file.jpeg'; only adds quotes if needed.
|
||||
* `function`: Run custom python function to filter value; use in format 'function:/path/to/file.py::function_name'. See example at https://github.com/RhetTbull/osxphotos/blob/master/examples/template_filter.py
|
||||
* ``split(x)``\ : Split value into a list of values using x as delimiter, e.g. 'value1;value2' => ['value1', 'value2'] if used with split(;).
|
||||
* ``autosplit``\ : Automatically split delimited string into separate values; will split strings delimited by comma, semicolon, or space, e.g. 'value1,value2' => ['value1', 'value2'].
|
||||
* `chop(x)`: Remove x characters off the end of value, e.g. chop(1): 'Value' => 'Valu'; when applied to a list, chops characters from each list value, e.g. chop(1): ['travel', 'beach']=> ['trave', 'beac'].
|
||||
* `chomp(x)`: Remove x characters from the beginning of value, e.g. chomp(1): ['Value'] => ['alue']; when applied to a list, removes characters from each list value, e.g. chomp(1): ['travel', 'beach']=> ['ravel', 'each'].
|
||||
* ``sort``\ : Sort list of values, e.g. ['c', 'b', 'a'] => ['a', 'b', 'c'].
|
||||
* ``rsort``\ : Sort list of values in reverse order, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
|
||||
* ``reverse``\ : Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
|
||||
* ``uniq``\ : Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b', 'c'].
|
||||
* `join(x)`: Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] => 'a,b,c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. e.g. join(): ['a', 'b', 'c'] => 'abc'.
|
||||
* `append(x)`: Append x to list of values, e.g. append(d): ['a', 'b', 'c'] => ['a', 'b', 'c', 'd'].
|
||||
* `prepend(x)`: Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] => ['d', 'a', 'b', 'c'].
|
||||
* `remove(x)`: Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] => ['a', 'c'].
|
||||
* `slice(start:stop:step)`: Slice list using same semantics as Python's list slicing, e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] => ['b', 'd']; slice(1:): ['a', 'b', 'c', 'd'] => ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c']; slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also sslice().
|
||||
* `sslice(start:stop:step)`: [s(tring) slice] Slice values in a list using same semantics as Python's string slicing, e.g. sslice(1:3):'abcd => 'bc'; sslice(1:4:2): 'abcd' => 'bd', etc. See also slice().
|
||||
|
||||
e.g. if Photo keywords are ``["FOO","bar"]``\ :
|
||||
|
||||
@@ -59,8 +75,6 @@ e.g. if Photo description is "my description":
|
||||
|
||||
* ``"{descr|titlecase}"`` renders to: ``"My Description"``
|
||||
|
||||
``(path_sep)``\ : optional path separator to use when joining path-like fields, for example ``{folder_album}``. Default is "/".
|
||||
|
||||
e.g. If Photo is in ``Album1`` in ``Folder1``\ :
|
||||
|
||||
|
||||
@@ -106,7 +120,7 @@ This can be used to rename files as well, for example:
|
||||
|
||||
This renames any photo that is a favorite as 'Favorite-ImageName.jpg' (where 'ImageName.jpg' is the original name of the photo) and all other photos with the unmodified original name.
|
||||
|
||||
``?bool_value``\ : Template fields may be evaluated as boolean (True/False) by appending "?" after the field name (and following "(path_sep)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is ``"{hdr}"``\ ) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is ``"{title}"``\ ) then the default value following a "," will be used.
|
||||
``?bool_value``\ : Template fields may be evaluated as boolean (True/False) by appending "?" after the field name (and following "(field_arg)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is ``"{hdr}"``\ ) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is ``"{title}"``\ ) then the default value following a "," will be used.
|
||||
|
||||
e.g. if photo is an HDR image,
|
||||
|
||||
@@ -141,6 +155,16 @@ If you want to include "{" or "}" in the output, use "{openbrace}" or "{closebra
|
||||
|
||||
e.g. ``"{created.year}/{openbrace}{title}{closebrace}"`` would result in ``"2020/{Photo Title}"``.
|
||||
|
||||
**Variables**
|
||||
|
||||
You can define variables for later use in the template string using the format ``{var:NAME,VALUE}``. Variables may then be referenced using the format ``%NAME``. For example: ``{var:foo,bar}`` defines the variable ``%foo`` to have value ``bar``. This can be useful if you want to re-use a complex template value in multiple places within your template string or for allowing the use of characters that would otherwise be prohibited in a template string. For example, the "pipe" (\ ``|``\ ) character is not allowed in a find/replace pair but you can get around this limitation like so: ``{var:pipe,{pipe}}{title[-,%pipe]}`` which replaces the ``-`` character with ``|`` (the value of ``%pipe``\ ).
|
||||
|
||||
Variables can also be referenced as fields in the template string, for example: ``{var:year,created.year}{original_name}-{%year}``. In some cases, use of variables can make your template string more readable. Variables can be used as template fields, as values for filters, as values for conditional operations, or as default values. When used as a conditional value or default value, variables should be treated like any other field and enclosed in braces as conditional and default values are evaluated as template strings. For example: ``{var:name,Katie}{person contains {%name}?{%name},Not-{%name}}``.
|
||||
|
||||
If you need to use a ``%`` (percent sign character), you can escape the percent sign by using ``%%``. You can also use the ``{percent}`` template field where a template field is required. For example:
|
||||
|
||||
``{title[:,%%]}`` replaces the ``:`` with ``%`` and ``{title contains Foo?{title}{percent},{title}}`` adds ``%`` to the title if it contains ``Foo``.
|
||||
|
||||
Template Substitutions
|
||||
----------------------
|
||||
|
||||
@@ -285,12 +309,14 @@ Template Substitutions
|
||||
- The moment title of the photo
|
||||
* - {uuid}
|
||||
- Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'
|
||||
* - {shortuuid}
|
||||
- A shorter representation of photo's internal universally unique identifier (UUID) for the photo, a 22-character string unique to the photo, e.g. 'JYsxugP9UjetmCbBCHXcmu'
|
||||
* - {id}
|
||||
- A unique number for the photo based on its primary key in the Photos database. A sequential integer, e.g. 1, 2, 3...etc. Each asset associated with a photo (e.g. an image and Live Photo preview) will share the same id. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{id:05d}' which results in 00001, 00002, 00003...etc.
|
||||
* - {album_seq}
|
||||
- An integer, starting at 0, indicating the photo's index (sequence) in the containing album. Only valid when used in a '--filename' template and only when '{album}' or '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{album\ *seq}*\ {original_name}"'. To start counting at a value other than 0, append append a period and the starting value to the field name. For example, to start counting at 1 instead of 0: '{album_seq.1}'. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{album_seq:05d}' which results in 00000, 00001, 00002...etc. This may result in incorrect sequences if you have duplicate albums with the same name; see also '{folder_album_seq}'.
|
||||
- An integer, starting at 0, indicating the photo's index (sequence) in the containing album. Only valid when used in a '--filename' template and only when '{album}' or '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{album\ *seq}*\ {original_name}"'. To start counting at a value other than 0, append append '(starting_value)' to the field name. For example, to start counting at 1 instead of 0: '{album_seq(1)}'. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{album_seq:05d}' which results in 00000, 00001, 00002...etc. To format while also using a starting value: '{album_seq:05d(1)}' which results in 0001, 00002...etc.This may result in incorrect sequences if you have duplicate albums with the same name; see also '{folder_album_seq}'.
|
||||
* - {folder_album_seq}
|
||||
- An integer, starting at 0, indicating the photo's index (sequence) in the containing album and folder path. Only valid when used in a '--filename' template and only when '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{folder_album\ *seq}*\ {original_name}"'. To start counting at a value other than 0, append append a period and the starting value to the field name. For example, to start counting at 1 instead of 0: '{folder_album_seq.1}' May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{folder_album_seq:05d}' which results in 00000, 00001, 00002...etc. This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also '{album_seq}'.
|
||||
- An integer, starting at 0, indicating the photo's index (sequence) in the containing album and folder path. Only valid when used in a '--filename' template and only when '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{folder_album\ *seq}*\ {original_name}"'. To start counting at a value other than 0, append '(starting_value)' to the field name. For example, to start counting at 1 instead of 0: '{folder_album_seq(1)}' May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{folder_album_seq:05d}' which results in 00000, 00001, 00002...etc. To format while also using a starting value: '{folder_album_seq:05d(1)}' which results in 0001, 00002...etc.This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also '{album_seq}'.
|
||||
* - {comma}
|
||||
- A comma: ','
|
||||
* - {semicolon}
|
||||
@@ -320,7 +346,7 @@ Template Substitutions
|
||||
* - {crlf}
|
||||
- a carriage return + line feed: '\r\n'
|
||||
* - {osxphotos_version}
|
||||
- The osxphotos version, e.g. '0.49.2'
|
||||
- The osxphotos version, e.g. '0.50.4'
|
||||
* - {osxphotos_cmd_line}
|
||||
- The full command line used to run osxphotos
|
||||
* - {album}
|
||||
@@ -361,6 +387,8 @@ Template Substitutions
|
||||
- Use in form '{shell_quote,TEMPLATE}'; quotes the rendered TEMPLATE value(s) for safe usage in the shell, e.g. My file.jpeg => 'My file.jpeg'; only adds quotes if needed.
|
||||
* - {strip}
|
||||
- Use in form '{strip,TEMPLATE}'; strips whitespace from begining and end of rendered TEMPLATE value(s).
|
||||
* - {format}
|
||||
- Use in form, '{format:TYPE:FORMAT,TEMPLATE}'; converts TEMPLATE value to TYPE then formats the value using Python string formatting codes specified by FORMAT; TYPE is one of: 'int', 'float', or 'str'. For example, '{format:float:.1f,{exiftool:EXIF:FocalLength}}' will format focal length to 1 decimal place (e.g. '100.0').
|
||||
* - {function}
|
||||
- Execute a python function from an external file and use return value as template substitution. Use in format: {function:file.py::function_name} where 'file.py' is the name of the python file and 'function_name' is the name of the function to call. The function will be passed the PhotoInfo object for the photo. See https://github.com/RhetTbull/osxphotos/blob/master/examples/template_function.py for an example of how to implement a template function.
|
||||
|
||||
|
||||
@@ -218,6 +218,15 @@ A powerful feature of Photos is that it uses machine learning algorithms to auto
|
||||
|
||||
``osxphotos export /path/to/export --exiftool --keyword-template "{label}"``
|
||||
|
||||
Removing a keyword during export
|
||||
--------------------------------
|
||||
|
||||
If some of your photos contain a keyword you do not want to be added to the exported file with ``--exiftool``\ , you can use the template system to remove the keyword from the exported file. For example, if you want to remove the keyword "MyKeyword" from all your photos:
|
||||
|
||||
``osxphotos export /path/to/export --exiftool --keyword-template "{keyword|remove(MyKeyword)}" --replace-keywords``
|
||||
|
||||
In this example, ``|remove(MyKeyword)`` is a filter which removes ``MyKeyword`` from the keyword list of every photo being processed. The ``--replace-keywords`` option instructs osxphotos to replace the keywords in the exported file with the filtered keywords from ``--keyword-template``.
|
||||
|
||||
**Note**\ : When evaluating templates for ``--directory`` and ``--filename``\ , osxphotos inserts the automatic default value "_" for any template field which is null (empty or blank). This is to ensure that there's never a null directory or filename created. For metadata templates such as ``--keyword-template``\ , osxphotos does not provide an automatic default value thus if the template field is null, no keyword would be created. Of course, you can provide a default value if desired and osxphotos will use this. For example, to add "nolabel" as a keyword for any photo that doesn't have labels:
|
||||
|
||||
``osxphotos export /path/to/export --exiftool --keyword-template "{label,nolabel}"``
|
||||
|
||||
2
docs/_static/documentation_options.js
vendored
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.49.2',
|
||||
VERSION: '0.50.4',
|
||||
LANGUAGE: 'None',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Template System" href="template_help.html" /><link rel="prev" title="OSXPhotos Tutorial" href="tutorial.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>OSXPhotos Command Line Interface (CLI) - osxphotos 0.49.2 documentation</title>
|
||||
<title>OSXPhotos Command Line Interface (CLI) - osxphotos 0.50.4 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.49.2 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.50.4 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.49.2 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.4 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">
|
||||
@@ -1278,6 +1278,26 @@ to modify this behavior.</p>
|
||||
<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-uuid-files">
|
||||
<span class="sig-name descname"><span class="pre">--uuid-files</span></span><span class="sig-prename descclassname"> <span class="pre"><UUID></span></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-uuid-files" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>List exported files associated with UUID.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-uuid-info">
|
||||
<span class="sig-name descname"><span class="pre">--uuid-info</span></span><span class="sig-prename descclassname"> <span class="pre"><UUID></span></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-uuid-info" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Print information about UUID contained in the database.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-delete-uuid">
|
||||
<span class="sig-name descname"><span class="pre">--delete-uuid</span></span><span class="sig-prename descclassname"> <span class="pre"><UUID></span></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-delete-uuid" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Delete all data associated with UUID from the database.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-delete-file">
|
||||
<span class="sig-name descname"><span class="pre">--delete-file</span></span><span class="sig-prename descclassname"> <span class="pre"><FILE_PATH></span></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-delete-file" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Delete all data associated with FILE_PATH from the database; does not delete the actual exported file if it exists, only the data 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"><REPORT_FILE</span> <span class="pre">RUN_ID></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 didn’t 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 today’s date. See also –append.</p>
|
||||
@@ -1361,6 +1381,43 @@ to modify this behavior.</p>
|
||||
<dd><p>Optional argument(s)</p>
|
||||
</dd></dl>
|
||||
</section>
|
||||
<section id="osxphotos-inspect">
|
||||
<h3>inspect<a class="headerlink" href="#osxphotos-inspect" title="Permalink to this headline">#</a></h3>
|
||||
<p>Interactively inspect photos selected in Photos.</p>
|
||||
<p>Open Photos then run <cite>osxphotos inspect</cite> in the terminal.
|
||||
As you select a photo in Photos, inspect will display metadata about the photo.
|
||||
Press Ctrl+C to exit when done.
|
||||
Works best with a modern terminal like iTerm2 or Kitty.</p>
|
||||
<div class="highlight-shell notranslate"><div class="highlight"><pre><span></span>osxphotos inspect <span class="o">[</span>OPTIONS<span class="o">]</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p class="rubric">Options</p>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-inspect-t">
|
||||
<span id="cmdoption-osxphotos-inspect-detect-text"></span><span class="sig-name descname"><span class="pre">-t</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">--detect-text</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-inspect-t" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Detect text in photos</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-inspect-T">
|
||||
<span id="cmdoption-osxphotos-inspect-template"></span><span class="sig-name descname"><span class="pre">-T</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">--template</span></span><span class="sig-prename descclassname"> <span class="pre"><TEMPLATE></span></span><a class="headerlink" href="#cmdoption-osxphotos-inspect-T" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Template string to render for each photo using template preview mode. Useful for testing templates for export; may be repeated to test multiple templates. If –template/-T is used, other inspection data will not be displayed.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-inspect-theme">
|
||||
<span class="sig-name descname"><span class="pre">--theme</span></span><span class="sig-prename descclassname"> <span class="pre"><THEME></span></span><a class="headerlink" href="#cmdoption-osxphotos-inspect-theme" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Specify the color theme to use for –verbose output. Valid themes are ‘dark’, ‘light’, ‘mono’, and ‘plain’. Defaults to ‘dark’ or ‘light’ depending on system dark mode setting.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Options</dt>
|
||||
<dd class="field-odd"><p>dark | light | mono | plain</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-inspect-db">
|
||||
<span class="sig-name descname"><span class="pre">--db</span></span><span class="sig-prename descclassname"> <span class="pre"><PHOTOS_LIBRARY_PATH></span></span><a class="headerlink" href="#cmdoption-osxphotos-inspect-db" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Specify Photos database path. Path to Photos library/database can be specified using either –db or directly as PHOTOS_LIBRARY positional argument. If neither –db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary</p>
|
||||
</dd></dl>
|
||||
</section>
|
||||
<section id="osxphotos-install">
|
||||
<h3>install<a class="headerlink" href="#osxphotos-install" title="Permalink to this headline">#</a></h3>
|
||||
<p>Install Python packages into the same environment as osxphotos</p>
|
||||
@@ -2705,6 +2762,7 @@ Commands:
|
||||
<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-inspect">inspect</a></li>
|
||||
<li><a class="reference internal" href="#osxphotos-install">install</a></li>
|
||||
<li><a class="reference internal" href="#osxphotos-keywords">keywords</a></li>
|
||||
<li><a class="reference internal" href="#osxphotos-labels">labels</a></li>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#" /><link rel="search" title="Search" href="search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Index - osxphotos 0.49.2 documentation</title>
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Index - osxphotos 0.50.4 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.49.2 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.50.4 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.49.2 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.4 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">
|
||||
@@ -402,6 +402,8 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-db">osxphotos-export command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-info-db">osxphotos-info command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-inspect-db">osxphotos-inspect command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-keywords-db">osxphotos-keywords command line option</a>
|
||||
</li>
|
||||
@@ -437,6 +439,20 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-theme-delete">osxphotos-theme command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--delete-file
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-exportdb-delete-file">osxphotos-exportdb command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--delete-uuid
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-exportdb-delete-uuid">osxphotos-exportdb command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -483,6 +499,13 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-exiftool-description-template">osxphotos-exiftool command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-description-template">osxphotos-export command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--detect-text
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-inspect-t">osxphotos-inspect command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1705,6 +1728,13 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-diff-s">osxphotos-diff command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--template
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-inspect-T">osxphotos-inspect command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1714,6 +1744,8 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-exiftool-theme">osxphotos-exiftool command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-theme">osxphotos-export command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-inspect-theme">osxphotos-inspect command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-theme">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
@@ -1872,6 +1904,13 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-query-uuid">osxphotos-query command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-uuid">osxphotos-repl command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--uuid-files
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-exportdb-uuid-files">osxphotos-exportdb command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1883,6 +1922,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>
|
||||
--uuid-info
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-exportdb-uuid-info">osxphotos-exportdb command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -2065,6 +2111,8 @@
|
||||
-T
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-inspect-T">osxphotos-inspect command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-T">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
@@ -2072,6 +2120,8 @@
|
||||
-t
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-inspect-t">osxphotos-inspect command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-t">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
@@ -2238,6 +2288,8 @@
|
||||
<li><a href="reference.html#osxphotos.SearchInfo.city">city (osxphotos.SearchInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.ExportDB.close">close() (osxphotos.ExportDB method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.cloud_metadata">cloud_metadata (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.QueryOptions.cloudasset">cloudasset (osxphotos.QueryOptions attribute)</a>
|
||||
</li>
|
||||
@@ -2290,10 +2342,14 @@
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.db_version">db_version (osxphotos.PhotosDB property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.QueryOptions.deleted">deleted (osxphotos.QueryOptions attribute)</a>
|
||||
<li><a href="reference.html#osxphotos.ExportDB.delete_data_for_filepath">delete_data_for_filepath() (osxphotos.ExportDB method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.ExportDB.delete_data_for_uuid">delete_data_for_uuid() (osxphotos.ExportDB method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.QueryOptions.deleted">deleted (osxphotos.QueryOptions attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.QueryOptions.deleted_only">deleted_only (osxphotos.QueryOptions attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.description">description (osxphotos.PhotoInfo property)</a>
|
||||
@@ -2357,14 +2413,18 @@
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoExporter.exiftool_json_sidecar">exiftool_json_sidecar() (osxphotos.PhotoExporter method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoTemplate.expand_variables">expand_variables() (osxphotos.PhotoTemplate method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoTemplate.expand_variables_to_str">expand_variables_to_str() (osxphotos.PhotoTemplate method)</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>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.export">(osxphotos.PhotoInfo method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.ExportOptions.export_as_hardlink">export_as_hardlink (osxphotos.ExportOptions attribute)</a>
|
||||
</li>
|
||||
<li>
|
||||
@@ -2386,6 +2446,8 @@
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="reference.html#osxphotos.ExportDB">ExportDB (class in osxphotos)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.ExportDBTemp">ExportDBTemp (class in osxphotos)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.ExportOptions">ExportOptions (class in osxphotos)</a>
|
||||
</li>
|
||||
@@ -2462,20 +2524,32 @@
|
||||
<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_exported_files">get_exported_files() (osxphotos.ExportDB method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoTemplate.get_field_values">get_field_values() (osxphotos.PhotoTemplate method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.ExportDB.get_file_record">get_file_record() (osxphotos.ExportDB method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.ExportDB.get_files_for_uuid">get_files_for_uuid() (osxphotos.ExportDB method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoTemplate.get_filter_values">get_filter_values() (osxphotos.PhotoTemplate method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoTemplate.get_format_values">get_format_values() (osxphotos.PhotoTemplate method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoTemplate.get_media_type">get_media_type() (osxphotos.PhotoTemplate method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotosDB.get_photo">get_photo() (osxphotos.PhotosDB method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoTemplate.get_photo_bool_attribute">get_photo_bool_attribute() (osxphotos.PhotoTemplate method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<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>
|
||||
</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.ExportDB.get_target_for_file">get_target_for_file() (osxphotos.ExportDB method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoTemplate.get_template_value">get_template_value() (osxphotos.PhotoTemplate method)</a>
|
||||
</li>
|
||||
@@ -3228,6 +3302,10 @@
|
||||
<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-delete-file">--delete-file</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-exportdb-delete-uuid">--delete-uuid</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-exportdb-dry-run">--dry-run</a>
|
||||
</li>
|
||||
@@ -3248,6 +3326,10 @@
|
||||
<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-uuid-files">--uuid-files</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-exportdb-uuid-info">--uuid-info</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-exportdb-vacuum">--vacuum</a>
|
||||
</li>
|
||||
@@ -3278,6 +3360,23 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-info-json">--json</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-info-arg-PHOTOS_LIBRARY">PHOTOS_LIBRARY</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
osxphotos-inspect command line option
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-inspect-db">--db</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-inspect-t">--detect-text</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-inspect-T">--template</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-inspect-theme">--theme</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-inspect-t">-t</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-inspect-T">-T</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -3313,6 +3412,8 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-labels-arg-PHOTOS_LIBRARY">PHOTOS_LIBRARY</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li>
|
||||
osxphotos-list command line option
|
||||
|
||||
@@ -3320,8 +3421,6 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-list-json">--json</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li>
|
||||
osxphotos-persons command line option
|
||||
|
||||
|
||||
@@ -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.49.2 documentation</title>
|
||||
<title>osxphotos 0.50.4 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.49.2 documentation</div></a>
|
||||
<a href="#"><div class="brand">osxphotos 0.50.4 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.49.2 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.4 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">
|
||||
@@ -216,6 +216,7 @@
|
||||
<li class="toctree-l2"><a class="reference internal" href="tutorial.html#missing-photos">Missing photos</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tutorial.html#exporting-to-external-disks">Exporting to external disks</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tutorial.html#exporting-metadata-with-exported-photos">Exporting metadata with exported photos</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tutorial.html#removing-a-keyword-during-export">Removing a keyword during export</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tutorial.html#sidecar-files">Sidecar files</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tutorial.html#updating-a-previous-export">Updating a previous export</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tutorial.html#dry-run">Dry Run</a></li>
|
||||
@@ -242,6 +243,7 @@
|
||||
<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-inspect">inspect</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-install">install</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-keywords">keywords</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-labels">labels</a></li>
|
||||
|
||||
BIN
docs/objects.inv
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Tutorial" href="tutorial.html" /><link rel="prev" title="Welcome to OSXPhotos’s documentation!" href="index.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>OSXPhotos - osxphotos 0.49.2 documentation</title>
|
||||
<title>OSXPhotos - osxphotos 0.50.4 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.49.2 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.50.4 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.49.2 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.4 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos python 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.49.2 documentation</title>
|
||||
<title>OSXPhotos Python Package Overview - osxphotos 0.50.4 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.49.2 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.50.4 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.49.2 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.4 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Python Module Index - osxphotos 0.49.2 documentation</title>
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Python Module Index - osxphotos 0.50.4 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.49.2 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.50.4 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.49.2 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.4 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="#" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Search - osxphotos 0.49.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.50.4 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.49.2 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.50.4 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.49.2 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.4 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="#" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Python 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.49.2 documentation</title>
|
||||
<title>OSXPhotos Template System - osxphotos 0.50.4 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.49.2 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.50.4 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.49.2 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.4 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">
|
||||
@@ -199,7 +199,7 @@
|
||||
<p>The templating system converts one or template statements, written in osxphotos metadata templating language, to one or more rendered values using information from the photo being processed.</p>
|
||||
<p>In its simplest form, a template statement has the form: <code class="docutils literal notranslate"><span class="pre">"{template_field}"</span></code>, for example <code class="docutils literal notranslate"><span class="pre">"{title}"</span></code> which would resolve to the title of the photo.</p>
|
||||
<p>Template statements may contain one or more modifiers. The full syntax is:</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">"pretext{delim+template_field:subfield|filter(path_sep)[find,replace]</span> <span class="pre">conditional?bool_value,default}posttext"</span></code></p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">"pretext{delim+template_field:subfield(field_arg)|filter[find,replace]</span> <span class="pre">conditional?bool_value,default}posttext"</span></code></p>
|
||||
<p>Template statements are white-space sensitive meaning that white space (spaces, tabs) changes the meaning of the template statement.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">pretext</span></code> and <code class="docutils literal notranslate"><span class="pre">posttext</span></code> are free form text. For example, if a photo has title “My Photo Title” the template statement <code class="docutils literal notranslate"><span class="pre">"The</span> <span class="pre">title</span> <span class="pre">of</span> <span class="pre">the</span> <span class="pre">photo</span> <span class="pre">is</span> <span class="pre">{title}"</span></code>, resolves to <code class="docutils literal notranslate"><span class="pre">"The</span> <span class="pre">title</span> <span class="pre">of</span> <span class="pre">the</span> <span class="pre">photo</span> <span class="pre">is</span> <span class="pre">My</span> <span class="pre">Photo</span> <span class="pre">Title"</span></code>. The <code class="docutils literal notranslate"><span class="pre">pretext</span></code> in this example is <code class="docutils literal notranslate"><span class="pre">"The</span> <span class="pre">title</span> <span class="pre">if</span> <span class="pre">the</span> <span class="pre">photo</span> <span class="pre">is</span> <span class="pre">"</span></code> and the template_field is <code class="docutils literal notranslate"><span class="pre">{title}</span></code>.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">delim</span></code>: optional delimiter string to use when expanding multi-valued template values in-place</p>
|
||||
@@ -213,6 +213,7 @@
|
||||
</ul>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">template_field</span></code>: The template field to resolve. See <a class="reference external" href="#template-substitutions">Template Substitutions</a> for full list of template fields.</p>
|
||||
<p><cite>:subfield</cite>: Some templates have sub-fields, For example, <cite>{exiftool:IPTC:Make}`</cite>; the template_field is``exiftool<code class="docutils literal notranslate"><span class="pre">and</span> <span class="pre">the</span> <span class="pre">sub-field</span> <span class="pre">is</span></code>IPTC:Make`.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">(field_arg)</span></code>: optional arguments to pass to the field; for example, with <code class="docutils literal notranslate"><span class="pre">{folder_album}</span></code> this is used to pass the path separator used for joining folders and albums when rendering the field (default is “/” for <code class="docutils literal notranslate"><span class="pre">{folder_album}</span></code>).</p>
|
||||
<p><cite>|filter</cite>: You may optionally append one or more filter commands to the end of the template field using the vertical pipe (‘|’) symbol. Filters may be combined, separated by ‘|’ as in: <code class="docutils literal notranslate"><span class="pre">{keyword|capitalize|parens}</span></code>.</p>
|
||||
<p>Valid filters are:</p>
|
||||
<ul class="simple">
|
||||
@@ -226,6 +227,20 @@
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">brackets</span></code>: Enclose value in brackets, e.g. ‘value’ => ‘[value]’</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">shell_quote</span></code>: Quotes the value for safe usage in the shell, e.g. My file.jpeg => ‘My file.jpeg’; only adds quotes if needed.</p></li>
|
||||
<li><p><cite>function</cite>: Run custom python function to filter value; use in format ‘function:/path/to/file.py::function_name’. See example at https://github.com/RhetTbull/osxphotos/blob/master/examples/template_filter.py</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">split(x)</span></code>: Split value into a list of values using x as delimiter, e.g. ‘value1;value2’ => [‘value1’, ‘value2’] if used with split(;).</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">autosplit</span></code>: Automatically split delimited string into separate values; will split strings delimited by comma, semicolon, or space, e.g. ‘value1,value2’ => [‘value1’, ‘value2’].</p></li>
|
||||
<li><p><cite>chop(x)</cite>: Remove x characters off the end of value, e.g. chop(1): ‘Value’ => ‘Valu’; when applied to a list, chops characters from each list value, e.g. chop(1): [‘travel’, ‘beach’]=> [‘trave’, ‘beac’].</p></li>
|
||||
<li><p><cite>chomp(x)</cite>: Remove x characters from the beginning of value, e.g. chomp(1): [‘Value’] => [‘alue’]; when applied to a list, removes characters from each list value, e.g. chomp(1): [‘travel’, ‘beach’]=> [‘ravel’, ‘each’].</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">sort</span></code>: Sort list of values, e.g. [‘c’, ‘b’, ‘a’] => [‘a’, ‘b’, ‘c’].</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">rsort</span></code>: Sort list of values in reverse order, e.g. [‘a’, ‘b’, ‘c’] => [‘c’, ‘b’, ‘a’].</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">reverse</span></code>: Reverse order of values, e.g. [‘a’, ‘b’, ‘c’] => [‘c’, ‘b’, ‘a’].</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">uniq</span></code>: Remove duplicate values, e.g. [‘a’, ‘b’, ‘c’, ‘b’, ‘a’] => [‘a’, ‘b’, ‘c’].</p></li>
|
||||
<li><p><cite>join(x)</cite>: Join list of values with delimiter x, e.g. join(,): [‘a’, ‘b’, ‘c’] => ‘a,b,c’; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.May optionally be used without an argument, that is ‘join()’ which joins values together with no delimiter. e.g. join(): [‘a’, ‘b’, ‘c’] => ‘abc’.</p></li>
|
||||
<li><p><cite>append(x)</cite>: Append x to list of values, e.g. append(d): [‘a’, ‘b’, ‘c’] => [‘a’, ‘b’, ‘c’, ‘d’].</p></li>
|
||||
<li><p><cite>prepend(x)</cite>: Prepend x to list of values, e.g. prepend(d): [‘a’, ‘b’, ‘c’] => [‘d’, ‘a’, ‘b’, ‘c’].</p></li>
|
||||
<li><p><cite>remove(x)</cite>: Remove x from list of values, e.g. remove(b): [‘a’, ‘b’, ‘c’] => [‘a’, ‘c’].</p></li>
|
||||
<li><p><cite>slice(start:stop:step)</cite>: Slice list using same semantics as Python’s list slicing, e.g. slice(1:3): [‘a’, ‘b’, ‘c’, ‘d’] => [‘b’, ‘c’]; slice(1:4:2): [‘a’, ‘b’, ‘c’, ‘d’] => [‘b’, ‘d’]; slice(1:): [‘a’, ‘b’, ‘c’, ‘d’] => [‘b’, ‘c’, ‘d’]; slice(:-1): [‘a’, ‘b’, ‘c’, ‘d’] => [‘a’, ‘b’, ‘c’]; slice(::-1): [‘a’, ‘b’, ‘c’, ‘d’] => [‘d’, ‘c’, ‘b’, ‘a’]. See also sslice().</p></li>
|
||||
<li><p><cite>sslice(start:stop:step)</cite>: [s(tring) slice] Slice values in a list using same semantics as Python’s string slicing, e.g. sslice(1:3):’abcd => ‘bc’; sslice(1:4:2): ‘abcd’ => ‘bd’, etc. See also slice().</p></li>
|
||||
</ul>
|
||||
<p>e.g. if Photo keywords are <code class="docutils literal notranslate"><span class="pre">["FOO","bar"]</span></code>:</p>
|
||||
<ul class="simple">
|
||||
@@ -238,7 +253,6 @@
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">"{descr|titlecase}"</span></code> renders to: <code class="docutils literal notranslate"><span class="pre">"My</span> <span class="pre">Description"</span></code></p></li>
|
||||
</ul>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">(path_sep)</span></code>: optional path separator to use when joining path-like fields, for example <code class="docutils literal notranslate"><span class="pre">{folder_album}</span></code>. Default is “/”.</p>
|
||||
<p>e.g. If Photo is in <code class="docutils literal notranslate"><span class="pre">Album1</span></code> in <code class="docutils literal notranslate"><span class="pre">Folder1</span></code>:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">"{folder_album}"</span></code> renders to <code class="docutils literal notranslate"><span class="pre">["Folder1/Album1"]</span></code></p></li>
|
||||
@@ -274,7 +288,7 @@
|
||||
<p>This can be used to rename files as well, for example:
|
||||
<code class="docutils literal notranslate"><span class="pre">--filename</span> <span class="pre">"{favorite?Favorite-{original_name},{original_name}}"</span></code></p>
|
||||
<p>This renames any photo that is a favorite as ‘Favorite-ImageName.jpg’ (where ‘ImageName.jpg’ is the original name of the photo) and all other photos with the unmodified original name.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">?bool_value</span></code>: Template fields may be evaluated as boolean (True/False) by appending “?” after the field name (and following “(path_sep)” or “[find/replace]”. If a field is True (e.g. photo is HDR and field is <code class="docutils literal notranslate"><span class="pre">"{hdr}"</span></code>) or has any value, the value following the “?” will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is <code class="docutils literal notranslate"><span class="pre">"{title}"</span></code>) then the default value following a “,” will be used.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">?bool_value</span></code>: Template fields may be evaluated as boolean (True/False) by appending “?” after the field name (and following “(field_arg)” or “[find/replace]”. If a field is True (e.g. photo is HDR and field is <code class="docutils literal notranslate"><span class="pre">"{hdr}"</span></code>) or has any value, the value following the “?” will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is <code class="docutils literal notranslate"><span class="pre">"{title}"</span></code>) then the default value following a “,” will be used.</p>
|
||||
<p>e.g. if photo is an HDR image,</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">"{hdr?ISHDR,NOTHDR}"</span></code> renders to <code class="docutils literal notranslate"><span class="pre">"ISHDR"</span></code></p></li>
|
||||
@@ -298,6 +312,11 @@
|
||||
<p>Either or both bool_value or default (False value) may be empty which would result in empty string <code class="docutils literal notranslate"><span class="pre">""</span></code> when rendered.</p>
|
||||
<p>If you want to include “{” or “}” in the output, use “{openbrace}” or “{closebrace}” template substitution.</p>
|
||||
<p>e.g. <code class="docutils literal notranslate"><span class="pre">"{created.year}/{openbrace}{title}{closebrace}"</span></code> would result in <code class="docutils literal notranslate"><span class="pre">"2020/{Photo</span> <span class="pre">Title}"</span></code>.</p>
|
||||
<p><strong>Variables</strong></p>
|
||||
<p>You can define variables for later use in the template string using the format <code class="docutils literal notranslate"><span class="pre">{var:NAME,VALUE}</span></code>. Variables may then be referenced using the format <code class="docutils literal notranslate"><span class="pre">%NAME</span></code>. For example: <code class="docutils literal notranslate"><span class="pre">{var:foo,bar}</span></code> defines the variable <code class="docutils literal notranslate"><span class="pre">%foo</span></code> to have value <code class="docutils literal notranslate"><span class="pre">bar</span></code>. This can be useful if you want to re-use a complex template value in multiple places within your template string or for allowing the use of characters that would otherwise be prohibited in a template string. For example, the “pipe” (<code class="docutils literal notranslate"><span class="pre">|</span></code>) character is not allowed in a find/replace pair but you can get around this limitation like so: <code class="docutils literal notranslate"><span class="pre">{var:pipe,{pipe}}{title[-,%pipe]}</span></code> which replaces the <code class="docutils literal notranslate"><span class="pre">-</span></code> character with <code class="docutils literal notranslate"><span class="pre">|</span></code> (the value of <code class="docutils literal notranslate"><span class="pre">%pipe</span></code>).</p>
|
||||
<p>Variables can also be referenced as fields in the template string, for example: <code class="docutils literal notranslate"><span class="pre">{var:year,created.year}{original_name}-{%year}</span></code>. In some cases, use of variables can make your template string more readable. Variables can be used as template fields, as values for filters, as values for conditional operations, or as default values. When used as a conditional value or default value, variables should be treated like any other field and enclosed in braces as conditional and default values are evaluated as template strings. For example: <code class="docutils literal notranslate"><span class="pre">{var:name,Katie}{person</span> <span class="pre">contains</span> <span class="pre">{%name}?{%name},Not-{%name}}</span></code>.</p>
|
||||
<p>If you need to use a <code class="docutils literal notranslate"><span class="pre">%</span></code> (percent sign character), you can escape the percent sign by using <code class="docutils literal notranslate"><span class="pre">%%</span></code>. You can also use the <code class="docutils literal notranslate"><span class="pre">{percent}</span></code> template field where a template field is required. For example:</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">{title[:,%%]}</span></code> replaces the <code class="docutils literal notranslate"><span class="pre">:</span></code> with <code class="docutils literal notranslate"><span class="pre">%</span></code> and <code class="docutils literal notranslate"><span class="pre">{title</span> <span class="pre">contains</span> <span class="pre">Foo?{title}{percent},{title}}</span></code> adds <code class="docutils literal notranslate"><span class="pre">%</span></code> to the title if it contains <code class="docutils literal notranslate"><span class="pre">Foo</span></code>.</p>
|
||||
<section id="id1">
|
||||
<h2>Template Substitutions<a class="headerlink" href="#id1" title="Permalink to this headline">#</a></h2>
|
||||
<div class="table-wrapper"><table class="docutils align-default">
|
||||
@@ -515,120 +534,126 @@
|
||||
<tr class="row-odd"><td><p>{uuid}</p></td>
|
||||
<td><p>Photo’s internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. ‘128FB4C6-0B16-4E7D-9108-FB2E90DA1546’</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{id}</p></td>
|
||||
<tr class="row-even"><td><p>{shortuuid}</p></td>
|
||||
<td><p>A shorter representation of photo’s internal universally unique identifier (UUID) for the photo, a 22-character string unique to the photo, e.g. ‘JYsxugP9UjetmCbBCHXcmu’</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{id}</p></td>
|
||||
<td><p>A unique number for the photo based on its primary key in the Photos database. A sequential integer, e.g. 1, 2, 3…etc. Each asset associated with a photo (e.g. an image and Live Photo preview) will share the same id. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use ‘{id:05d}’ which results in 00001, 00002, 00003…etc.</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{album_seq}</p></td>
|
||||
<td><p>An integer, starting at 0, indicating the photo’s index (sequence) in the containing album. Only valid when used in a ‘–filename’ template and only when ‘{album}’ or ‘{folder_album}’ is used in the ‘–directory’ template. For example ‘–directory “{folder_album}” –filename “{album<em>seq}</em>{original_name}”’. To start counting at a value other than 0, append append a period and the starting value to the field name. For example, to start counting at 1 instead of 0: ‘{album_seq.1}’. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use ‘{album_seq:05d}’ which results in 00000, 00001, 00002…etc. This may result in incorrect sequences if you have duplicate albums with the same name; see also ‘{folder_album_seq}’.</p></td>
|
||||
<tr class="row-even"><td><p>{album_seq}</p></td>
|
||||
<td><p>An integer, starting at 0, indicating the photo’s index (sequence) in the containing album. Only valid when used in a ‘–filename’ template and only when ‘{album}’ or ‘{folder_album}’ is used in the ‘–directory’ template. For example ‘–directory “{folder_album}” –filename “{album<em>seq}</em>{original_name}”’. To start counting at a value other than 0, append append ‘(starting_value)’ to the field name. For example, to start counting at 1 instead of 0: ‘{album_seq(1)}’. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use ‘{album_seq:05d}’ which results in 00000, 00001, 00002…etc. To format while also using a starting value: ‘{album_seq:05d(1)}’ which results in 0001, 00002…etc.This may result in incorrect sequences if you have duplicate albums with the same name; see also ‘{folder_album_seq}’.</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{folder_album_seq}</p></td>
|
||||
<td><p>An integer, starting at 0, indicating the photo’s index (sequence) in the containing album and folder path. Only valid when used in a ‘–filename’ template and only when ‘{folder_album}’ is used in the ‘–directory’ template. For example ‘–directory “{folder_album}” –filename “{folder_album<em>seq}</em>{original_name}”’. To start counting at a value other than 0, append append a period and the starting value to the field name. For example, to start counting at 1 instead of 0: ‘{folder_album_seq.1}’ May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use ‘{folder_album_seq:05d}’ which results in 00000, 00001, 00002…etc. This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also ‘{album_seq}’.</p></td>
|
||||
<tr class="row-odd"><td><p>{folder_album_seq}</p></td>
|
||||
<td><p>An integer, starting at 0, indicating the photo’s index (sequence) in the containing album and folder path. Only valid when used in a ‘–filename’ template and only when ‘{folder_album}’ is used in the ‘–directory’ template. For example ‘–directory “{folder_album}” –filename “{folder_album<em>seq}</em>{original_name}”’. To start counting at a value other than 0, append ‘(starting_value)’ to the field name. For example, to start counting at 1 instead of 0: ‘{folder_album_seq(1)}’ May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use ‘{folder_album_seq:05d}’ which results in 00000, 00001, 00002…etc. To format while also using a starting value: ‘{folder_album_seq:05d(1)}’ which results in 0001, 00002…etc.This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also ‘{album_seq}’.</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{comma}</p></td>
|
||||
<tr class="row-even"><td><p>{comma}</p></td>
|
||||
<td><p>A comma: ‘,’</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{semicolon}</p></td>
|
||||
<tr class="row-odd"><td><p>{semicolon}</p></td>
|
||||
<td><p>A semicolon: ‘;’</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{questionmark}</p></td>
|
||||
<tr class="row-even"><td><p>{questionmark}</p></td>
|
||||
<td><p>A question mark: ‘?’</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{pipe}</p></td>
|
||||
<tr class="row-odd"><td><p>{pipe}</p></td>
|
||||
<td><p>A vertical pipe: ‘|’</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{openbrace}</p></td>
|
||||
<tr class="row-even"><td><p>{openbrace}</p></td>
|
||||
<td><p>An open brace: ‘{’</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{closebrace}</p></td>
|
||||
<tr class="row-odd"><td><p>{closebrace}</p></td>
|
||||
<td><p>A close brace: ‘}’</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{openparens}</p></td>
|
||||
<tr class="row-even"><td><p>{openparens}</p></td>
|
||||
<td><p>An open parentheses: ‘(’</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{closeparens}</p></td>
|
||||
<tr class="row-odd"><td><p>{closeparens}</p></td>
|
||||
<td><p>A close parentheses: ‘)’</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{openbracket}</p></td>
|
||||
<tr class="row-even"><td><p>{openbracket}</p></td>
|
||||
<td><p>An open bracket: ‘[’</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{closebracket}</p></td>
|
||||
<tr class="row-odd"><td><p>{closebracket}</p></td>
|
||||
<td><p>A close bracket: ‘]’</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{newline}</p></td>
|
||||
<tr class="row-even"><td><p>{newline}</p></td>
|
||||
<td><p>A newline: ‘n’</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{lf}</p></td>
|
||||
<tr class="row-odd"><td><p>{lf}</p></td>
|
||||
<td><p>A line feed: ‘n’, alias for {newline}</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{cr}</p></td>
|
||||
<tr class="row-even"><td><p>{cr}</p></td>
|
||||
<td><p>A carriage return: ‘r’</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{crlf}</p></td>
|
||||
<tr class="row-odd"><td><p>{crlf}</p></td>
|
||||
<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.49.2’</p></td>
|
||||
<tr class="row-even"><td><p>{osxphotos_version}</p></td>
|
||||
<td><p>The osxphotos version, e.g. ‘0.50.4’</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{osxphotos_cmd_line}</p></td>
|
||||
<tr class="row-odd"><td><p>{osxphotos_cmd_line}</p></td>
|
||||
<td><p>The full command line used to run osxphotos</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{album}</p></td>
|
||||
<tr class="row-even"><td><p>{album}</p></td>
|
||||
<td><p>Album(s) photo is contained in</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{folder_album}</p></td>
|
||||
<tr class="row-odd"><td><p>{folder_album}</p></td>
|
||||
<td><p>Folder path + album photo is contained in. e.g. ‘Folder/Subfolder/Album’ or just ‘Album’ if no enclosing folder</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{project}</p></td>
|
||||
<tr class="row-even"><td><p>{project}</p></td>
|
||||
<td><p>Project(s) photo is contained in (such as greeting cards, calendars, slideshows)</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{album_project}</p></td>
|
||||
<tr class="row-odd"><td><p>{album_project}</p></td>
|
||||
<td><p>Album(s) and project(s) photo is contained in; treats projects as regular albums</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{folder_album_project}</p></td>
|
||||
<tr class="row-even"><td><p>{folder_album_project}</p></td>
|
||||
<td><p>Folder path + album (includes projects as albums) photo is contained in. e.g. ‘Folder/Subfolder/Album’ or just ‘Album’ if no enclosing folder</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{keyword}</p></td>
|
||||
<tr class="row-odd"><td><p>{keyword}</p></td>
|
||||
<td><p>Keyword(s) assigned to photo</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{person}</p></td>
|
||||
<tr class="row-even"><td><p>{person}</p></td>
|
||||
<td><p>Person(s) / face(s) in a photo</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{label}</p></td>
|
||||
<tr class="row-odd"><td><p>{label}</p></td>
|
||||
<td><p>Image categorization label associated with a photo (Photos 5+ only). Labels are added automatically by Photos using machine learning algorithms to categorize images. These are not the same as {keyword} which refers to the user-defined keywords/tags applied in Photos.</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{label_normalized}</p></td>
|
||||
<tr class="row-even"><td><p>{label_normalized}</p></td>
|
||||
<td><p>All lower case version of ‘label’ (Photos 5+ only)</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{comment}</p></td>
|
||||
<tr class="row-odd"><td><p>{comment}</p></td>
|
||||
<td><p>Comment(s) on shared Photos; format is ‘Person name: comment text’ (Photos 5+ only)</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{exiftool}</p></td>
|
||||
<tr class="row-even"><td><p>{exiftool}</p></td>
|
||||
<td><p>Format: ‘{exiftool:GROUP:TAGNAME}’; use exiftool (https://exiftool.org) to extract metadata, in form GROUP:TAGNAME, from image. E.g. ‘{exiftool:EXIF:Make}’ to get camera make, or {exiftool:IPTC:Keywords} to extract keywords. See https://exiftool.org/TagNames/ for list of valid tag names. You must specify group (e.g. EXIF, IPTC, etc) as used in <code class="docutils literal notranslate"><span class="pre">exiftool</span> <span class="pre">-G</span></code>. exiftool must be installed in the path to use this template.</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{searchinfo.holiday}</p></td>
|
||||
<tr class="row-odd"><td><p>{searchinfo.holiday}</p></td>
|
||||
<td><p>Holiday names associated with a photo, e.g. ‘Christmas Day’; (Photos 5+ only, applied automatically by Photos’ image categorization algorithms).</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{searchinfo.activity}</p></td>
|
||||
<tr class="row-even"><td><p>{searchinfo.activity}</p></td>
|
||||
<td><p>Activities associated with a photo, e.g. ‘Sporting Event’; (Photos 5+ only, applied automatically by Photos’ image categorization algorithms).</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{searchinfo.venue}</p></td>
|
||||
<tr class="row-odd"><td><p>{searchinfo.venue}</p></td>
|
||||
<td><p>Venues associated with a photo, e.g. name of restaurant; (Photos 5+ only, applied automatically by Photos’ image categorization algorithms).</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{searchinfo.venue_type}</p></td>
|
||||
<tr class="row-even"><td><p>{searchinfo.venue_type}</p></td>
|
||||
<td><p>Venue types associated with a photo, e.g. ‘Restaurant’; (Photos 5+ only, applied automatically by Photos’ image categorization algorithms).</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{photo}</p></td>
|
||||
<tr class="row-odd"><td><p>{photo}</p></td>
|
||||
<td><p>Provides direct access to the PhotoInfo object for the photo. Must be used in format ‘{photo.property}’ where ‘property’ represents a PhotoInfo property. For example: ‘{photo.favorite}’ is the same as ‘{favorite}’ and ‘{photo.place.name}’ is the same as ‘{place.name}’. ‘{photo}’ provides access to properties that are not available as separate template fields but it assumes some knowledge of the underlying PhotoInfo class. See <a class="reference external" href="https://rhettbull.github.io/osxphotos/">https://rhettbull.github.io/osxphotos/</a> for additional documentation on the PhotoInfo class.</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{detected_text}</p></td>
|
||||
<tr class="row-even"><td><p>{detected_text}</p></td>
|
||||
<td><p>List of text strings found in the image after performing text detection. Using ‘{detected_text}’ will cause osxphotos to perform text detection on your photos using the built-in macOS text detection algorithms which will slow down your export. The results for each photo will be cached in the export database so that future exports with ‘–update’ do not need to reprocess each photo. You may pass a confidence threshold value between 0.0 and 1.0 after a colon as in ‘{detected_text:0.5}’; The default confidence threshold is 0.75. ‘{detected_text}’ works only on macOS Catalina (10.15) or later. Note: this feature is not the same thing as Live Text in macOS Monterey, which osxphotos does not yet support.</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{shell_quote}</p></td>
|
||||
<tr class="row-odd"><td><p>{shell_quote}</p></td>
|
||||
<td><p>Use in form ‘{shell_quote,TEMPLATE}’; quotes the rendered TEMPLATE value(s) for safe usage in the shell, e.g. My file.jpeg => ‘My file.jpeg’; only adds quotes if needed.</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{strip}</p></td>
|
||||
<tr class="row-even"><td><p>{strip}</p></td>
|
||||
<td><p>Use in form ‘{strip,TEMPLATE}’; strips whitespace from begining and end of rendered TEMPLATE value(s).</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{format}</p></td>
|
||||
<td><p>Use in form, ‘{format:TYPE:FORMAT,TEMPLATE}’; converts TEMPLATE value to TYPE then formats the value using Python string formatting codes specified by FORMAT; TYPE is one of: ‘int’, ‘float’, or ‘str’. For example, ‘{format:float:.1f,{exiftool:EXIF:FocalLength}}’ will format focal length to 1 decimal place (e.g. ‘100.0’).</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{function}</p></td>
|
||||
<td><p>Execute a python function from an external file and use return value as template substitution. Use in format: {function:file.py::function_name} where ‘file.py’ is the name of the python file and ‘function_name’ is the name of the function to call. The function will be passed the PhotoInfo object for the photo. See https://github.com/RhetTbull/osxphotos/blob/master/examples/template_function.py for an example of how to implement a template function.</p></td>
|
||||
</tr>
|
||||
|
||||
@@ -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.49.2 documentation</title>
|
||||
<title>OSXPhotos Tutorial - osxphotos 0.50.4 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.49.2 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.50.4 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.49.2 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.50.4 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">
|
||||
@@ -331,6 +331,12 @@
|
||||
<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>Folder2>Album”. If you did not include the <code class="docutils literal notranslate"><span class="pre">(>)</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>
|
||||
<p>A powerful feature of Photos is that it uses machine learning algorithms to automatically classify or label photos. These labels are used when you search for images in Photos but are not otherwise available to the user. osxphotos is able to read all the labels associated with a photo and makes those available through the template system via the <code class="docutils literal notranslate"><span class="pre">{label}</span></code>. Think of these as automatic keywords as opposed to the keywords you assign manually in Photos. One common use case is to use the automatic labels to create new keywords when exporting images so that these labels are embedded in the image’s metadata:</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> <span class="pre">--keyword-template</span> <span class="pre">"{label}"</span></code></p>
|
||||
</section>
|
||||
<section id="removing-a-keyword-during-export">
|
||||
<h2>Removing a keyword during export<a class="headerlink" href="#removing-a-keyword-during-export" title="Permalink to this headline">#</a></h2>
|
||||
<p>If some of your photos contain a keyword you do not want to be added to the exported file with <code class="docutils literal notranslate"><span class="pre">--exiftool</span></code>, you can use the template system to remove the keyword from the exported file. For example, if you want to remove the keyword “MyKeyword” from all your 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> <span class="pre">--keyword-template</span> <span class="pre">"{keyword|remove(MyKeyword)}"</span> <span class="pre">--replace-keywords</span></code></p>
|
||||
<p>In this example, <code class="docutils literal notranslate"><span class="pre">|remove(MyKeyword)</span></code> is a filter which removes <code class="docutils literal notranslate"><span class="pre">MyKeyword</span></code> from the keyword list of every photo being processed. The <code class="docutils literal notranslate"><span class="pre">--replace-keywords</span></code> option instructs osxphotos to replace the keywords in the exported file with the filtered keywords from <code class="docutils literal notranslate"><span class="pre">--keyword-template</span></code>.</p>
|
||||
<p><strong>Note</strong>: When evaluating templates for <code class="docutils literal notranslate"><span class="pre">--directory</span></code> and <code class="docutils literal notranslate"><span class="pre">--filename</span></code>, osxphotos inserts the automatic default value “_” for any template field which is null (empty or blank). This is to ensure that there’s never a null directory or filename created. For metadata templates such as <code class="docutils literal notranslate"><span class="pre">--keyword-template</span></code>, osxphotos does not provide an automatic default value thus if the template field is null, no keyword would be created. Of course, you can provide a default value if desired and osxphotos will use this. For example, to add “nolabel” as a keyword for any photo that doesn’t have labels:</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> <span class="pre">--keyword-template</span> <span class="pre">"{label,nolabel}"</span></code></p>
|
||||
</section>
|
||||
@@ -568,6 +574,7 @@ template fields.
|
||||
<li><a class="reference internal" href="#missing-photos">Missing photos</a></li>
|
||||
<li><a class="reference internal" href="#exporting-to-external-disks">Exporting to external disks</a></li>
|
||||
<li><a class="reference internal" href="#exporting-metadata-with-exported-photos">Exporting metadata with exported photos</a></li>
|
||||
<li><a class="reference internal" href="#removing-a-keyword-during-export">Removing a keyword during export</a></li>
|
||||
<li><a class="reference internal" href="#sidecar-files">Sidecar files</a></li>
|
||||
<li><a class="reference internal" href="#updating-a-previous-export">Updating a previous export</a></li>
|
||||
<li><a class="reference internal" href="#dry-run">Dry Run</a></li>
|
||||
|
||||
@@ -8,7 +8,7 @@ In its simplest form, a template statement has the form: ``"{template_field}"``\
|
||||
|
||||
Template statements may contain one or more modifiers. The full syntax is:
|
||||
|
||||
``"pretext{delim+template_field:subfield|filter(path_sep)[find,replace] conditional?bool_value,default}posttext"``
|
||||
``"pretext{delim+template_field:subfield(field_arg)|filter[find,replace] conditional?bool_value,default}posttext"``
|
||||
|
||||
Template statements are white-space sensitive meaning that white space (spaces, tabs) changes the meaning of the template statement.
|
||||
|
||||
@@ -30,6 +30,8 @@ e.g. if Photo keywords are ``["foo","bar"]``\ :
|
||||
|
||||
`:subfield`: Some templates have sub-fields, For example, `{exiftool:IPTC:Make}\ ``; the template_field is``\ exiftool\ ``and the sub-field is``\ IPTC:Make`.
|
||||
|
||||
``(field_arg)``\ : optional arguments to pass to the field; for example, with ``{folder_album}`` this is used to pass the path separator used for joining folders and albums when rendering the field (default is "/" for ``{folder_album}``\ ).
|
||||
|
||||
`|filter`: You may optionally append one or more filter commands to the end of the template field using the vertical pipe ('|') symbol. Filters may be combined, separated by '|' as in: ``{keyword|capitalize|parens}``.
|
||||
|
||||
Valid filters are:
|
||||
@@ -45,6 +47,20 @@ Valid filters are:
|
||||
* ``brackets``\ : Enclose value in brackets, e.g. 'value' => '[value]'
|
||||
* ``shell_quote``\ : Quotes the value for safe usage in the shell, e.g. My file.jpeg => 'My file.jpeg'; only adds quotes if needed.
|
||||
* `function`: Run custom python function to filter value; use in format 'function:/path/to/file.py::function_name'. See example at https://github.com/RhetTbull/osxphotos/blob/master/examples/template_filter.py
|
||||
* ``split(x)``\ : Split value into a list of values using x as delimiter, e.g. 'value1;value2' => ['value1', 'value2'] if used with split(;).
|
||||
* ``autosplit``\ : Automatically split delimited string into separate values; will split strings delimited by comma, semicolon, or space, e.g. 'value1,value2' => ['value1', 'value2'].
|
||||
* `chop(x)`: Remove x characters off the end of value, e.g. chop(1): 'Value' => 'Valu'; when applied to a list, chops characters from each list value, e.g. chop(1): ['travel', 'beach']=> ['trave', 'beac'].
|
||||
* `chomp(x)`: Remove x characters from the beginning of value, e.g. chomp(1): ['Value'] => ['alue']; when applied to a list, removes characters from each list value, e.g. chomp(1): ['travel', 'beach']=> ['ravel', 'each'].
|
||||
* ``sort``\ : Sort list of values, e.g. ['c', 'b', 'a'] => ['a', 'b', 'c'].
|
||||
* ``rsort``\ : Sort list of values in reverse order, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
|
||||
* ``reverse``\ : Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
|
||||
* ``uniq``\ : Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b', 'c'].
|
||||
* `join(x)`: Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] => 'a,b,c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. e.g. join(): ['a', 'b', 'c'] => 'abc'.
|
||||
* `append(x)`: Append x to list of values, e.g. append(d): ['a', 'b', 'c'] => ['a', 'b', 'c', 'd'].
|
||||
* `prepend(x)`: Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] => ['d', 'a', 'b', 'c'].
|
||||
* `remove(x)`: Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] => ['a', 'c'].
|
||||
* `slice(start:stop:step)`: Slice list using same semantics as Python's list slicing, e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] => ['b', 'd']; slice(1:): ['a', 'b', 'c', 'd'] => ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c']; slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also sslice().
|
||||
* `sslice(start:stop:step)`: [s(tring) slice] Slice values in a list using same semantics as Python's string slicing, e.g. sslice(1:3):'abcd => 'bc'; sslice(1:4:2): 'abcd' => 'bd', etc. See also slice().
|
||||
|
||||
e.g. if Photo keywords are ``["FOO","bar"]``\ :
|
||||
|
||||
@@ -59,8 +75,6 @@ e.g. if Photo description is "my description":
|
||||
|
||||
* ``"{descr|titlecase}"`` renders to: ``"My Description"``
|
||||
|
||||
``(path_sep)``\ : optional path separator to use when joining path-like fields, for example ``{folder_album}``. Default is "/".
|
||||
|
||||
e.g. If Photo is in ``Album1`` in ``Folder1``\ :
|
||||
|
||||
|
||||
@@ -106,7 +120,7 @@ This can be used to rename files as well, for example:
|
||||
|
||||
This renames any photo that is a favorite as 'Favorite-ImageName.jpg' (where 'ImageName.jpg' is the original name of the photo) and all other photos with the unmodified original name.
|
||||
|
||||
``?bool_value``\ : Template fields may be evaluated as boolean (True/False) by appending "?" after the field name (and following "(path_sep)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is ``"{hdr}"``\ ) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is ``"{title}"``\ ) then the default value following a "," will be used.
|
||||
``?bool_value``\ : Template fields may be evaluated as boolean (True/False) by appending "?" after the field name (and following "(field_arg)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is ``"{hdr}"``\ ) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is ``"{title}"``\ ) then the default value following a "," will be used.
|
||||
|
||||
e.g. if photo is an HDR image,
|
||||
|
||||
@@ -141,6 +155,16 @@ If you want to include "{" or "}" in the output, use "{openbrace}" or "{closebra
|
||||
|
||||
e.g. ``"{created.year}/{openbrace}{title}{closebrace}"`` would result in ``"2020/{Photo Title}"``.
|
||||
|
||||
**Variables**
|
||||
|
||||
You can define variables for later use in the template string using the format ``{var:NAME,VALUE}``. Variables may then be referenced using the format ``%NAME``. For example: ``{var:foo,bar}`` defines the variable ``%foo`` to have value ``bar``. This can be useful if you want to re-use a complex template value in multiple places within your template string or for allowing the use of characters that would otherwise be prohibited in a template string. For example, the "pipe" (\ ``|``\ ) character is not allowed in a find/replace pair but you can get around this limitation like so: ``{var:pipe,{pipe}}{title[-,%pipe]}`` which replaces the ``-`` character with ``|`` (the value of ``%pipe``\ ).
|
||||
|
||||
Variables can also be referenced as fields in the template string, for example: ``{var:year,created.year}{original_name}-{%year}``. In some cases, use of variables can make your template string more readable. Variables can be used as template fields, as values for filters, as values for conditional operations, or as default values. When used as a conditional value or default value, variables should be treated like any other field and enclosed in braces as conditional and default values are evaluated as template strings. For example: ``{var:name,Katie}{person contains {%name}?{%name},Not-{%name}}``.
|
||||
|
||||
If you need to use a ``%`` (percent sign character), you can escape the percent sign by using ``%%``. You can also use the ``{percent}`` template field where a template field is required. For example:
|
||||
|
||||
``{title[:,%%]}`` replaces the ``:`` with ``%`` and ``{title contains Foo?{title}{percent},{title}}`` adds ``%`` to the title if it contains ``Foo``.
|
||||
|
||||
Template Substitutions
|
||||
----------------------
|
||||
|
||||
@@ -285,12 +309,14 @@ Template Substitutions
|
||||
- The moment title of the photo
|
||||
* - {uuid}
|
||||
- Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'
|
||||
* - {shortuuid}
|
||||
- A shorter representation of photo's internal universally unique identifier (UUID) for the photo, a 22-character string unique to the photo, e.g. 'JYsxugP9UjetmCbBCHXcmu'
|
||||
* - {id}
|
||||
- A unique number for the photo based on its primary key in the Photos database. A sequential integer, e.g. 1, 2, 3...etc. Each asset associated with a photo (e.g. an image and Live Photo preview) will share the same id. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{id:05d}' which results in 00001, 00002, 00003...etc.
|
||||
* - {album_seq}
|
||||
- An integer, starting at 0, indicating the photo's index (sequence) in the containing album. Only valid when used in a '--filename' template and only when '{album}' or '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{album\ *seq}*\ {original_name}"'. To start counting at a value other than 0, append append a period and the starting value to the field name. For example, to start counting at 1 instead of 0: '{album_seq.1}'. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{album_seq:05d}' which results in 00000, 00001, 00002...etc. This may result in incorrect sequences if you have duplicate albums with the same name; see also '{folder_album_seq}'.
|
||||
- An integer, starting at 0, indicating the photo's index (sequence) in the containing album. Only valid when used in a '--filename' template and only when '{album}' or '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{album\ *seq}*\ {original_name}"'. To start counting at a value other than 0, append append '(starting_value)' to the field name. For example, to start counting at 1 instead of 0: '{album_seq(1)}'. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{album_seq:05d}' which results in 00000, 00001, 00002...etc. To format while also using a starting value: '{album_seq:05d(1)}' which results in 0001, 00002...etc.This may result in incorrect sequences if you have duplicate albums with the same name; see also '{folder_album_seq}'.
|
||||
* - {folder_album_seq}
|
||||
- An integer, starting at 0, indicating the photo's index (sequence) in the containing album and folder path. Only valid when used in a '--filename' template and only when '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{folder_album\ *seq}*\ {original_name}"'. To start counting at a value other than 0, append append a period and the starting value to the field name. For example, to start counting at 1 instead of 0: '{folder_album_seq.1}' May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{folder_album_seq:05d}' which results in 00000, 00001, 00002...etc. This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also '{album_seq}'.
|
||||
- An integer, starting at 0, indicating the photo's index (sequence) in the containing album and folder path. Only valid when used in a '--filename' template and only when '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{folder_album\ *seq}*\ {original_name}"'. To start counting at a value other than 0, append '(starting_value)' to the field name. For example, to start counting at 1 instead of 0: '{folder_album_seq(1)}' May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{folder_album_seq:05d}' which results in 00000, 00001, 00002...etc. To format while also using a starting value: '{folder_album_seq:05d(1)}' which results in 0001, 00002...etc.This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also '{album_seq}'.
|
||||
* - {comma}
|
||||
- A comma: ','
|
||||
* - {semicolon}
|
||||
@@ -320,7 +346,7 @@ Template Substitutions
|
||||
* - {crlf}
|
||||
- a carriage return + line feed: '\r\n'
|
||||
* - {osxphotos_version}
|
||||
- The osxphotos version, e.g. '0.49.2'
|
||||
- The osxphotos version, e.g. '0.50.4'
|
||||
* - {osxphotos_cmd_line}
|
||||
- The full command line used to run osxphotos
|
||||
* - {album}
|
||||
@@ -361,6 +387,8 @@ Template Substitutions
|
||||
- Use in form '{shell_quote,TEMPLATE}'; quotes the rendered TEMPLATE value(s) for safe usage in the shell, e.g. My file.jpeg => 'My file.jpeg'; only adds quotes if needed.
|
||||
* - {strip}
|
||||
- Use in form '{strip,TEMPLATE}'; strips whitespace from begining and end of rendered TEMPLATE value(s).
|
||||
* - {format}
|
||||
- Use in form, '{format:TYPE:FORMAT,TEMPLATE}'; converts TEMPLATE value to TYPE then formats the value using Python string formatting codes specified by FORMAT; TYPE is one of: 'int', 'float', or 'str'. For example, '{format:float:.1f,{exiftool:EXIF:FocalLength}}' will format focal length to 1 decimal place (e.g. '100.0').
|
||||
* - {function}
|
||||
- Execute a python function from an external file and use return value as template substitution. Use in format: {function:file.py::function_name} where 'file.py' is the name of the python file and 'function_name' is the name of the function to call. The function will be passed the PhotoInfo object for the photo. See https://github.com/RhetTbull/osxphotos/blob/master/examples/template_function.py for an example of how to implement a template function.
|
||||
|
||||
|
||||
@@ -218,6 +218,15 @@ A powerful feature of Photos is that it uses machine learning algorithms to auto
|
||||
|
||||
``osxphotos export /path/to/export --exiftool --keyword-template "{label}"``
|
||||
|
||||
Removing a keyword during export
|
||||
--------------------------------
|
||||
|
||||
If some of your photos contain a keyword you do not want to be added to the exported file with ``--exiftool``\ , you can use the template system to remove the keyword from the exported file. For example, if you want to remove the keyword "MyKeyword" from all your photos:
|
||||
|
||||
``osxphotos export /path/to/export --exiftool --keyword-template "{keyword|remove(MyKeyword)}" --replace-keywords``
|
||||
|
||||
In this example, ``|remove(MyKeyword)`` is a filter which removes ``MyKeyword`` from the keyword list of every photo being processed. The ``--replace-keywords`` option instructs osxphotos to replace the keywords in the exported file with the filtered keywords from ``--keyword-template``.
|
||||
|
||||
**Note**\ : When evaluating templates for ``--directory`` and ``--filename``\ , osxphotos inserts the automatic default value "_" for any template field which is null (empty or blank). This is to ensure that there's never a null directory or filename created. For metadata templates such as ``--keyword-template``\ , osxphotos does not provide an automatic default value thus if the template field is null, no keyword would be created. Of course, you can provide a default value if desired and osxphotos will use this. For example, to add "nolabel" as a keyword for any photo that doesn't have labels:
|
||||
|
||||
``osxphotos export /path/to/export --exiftool --keyword-template "{label,nolabel}"``
|
||||
|
||||
40
examples/rating_to_favorites.py
Normal file
@@ -0,0 +1,40 @@
|
||||
""" Find photos that had an EXIF or XMP rating of 5 and mark them as favorites in Photos
|
||||
|
||||
To use this script, save it to a file, e.g. `rating_to_favorites.py` then
|
||||
run it with osxphotos (https://github.com/RhetTbull/osxphotos) via
|
||||
`osxphotos run rating_to_favorites.py`
|
||||
|
||||
You'll also need exiftool (https://exiftool.org/)
|
||||
"""
|
||||
|
||||
import photoscript
|
||||
|
||||
import osxphotos
|
||||
|
||||
# only find photos taken with SONY cameras, adjust to suit your use case
|
||||
CAMERA_MAKE = "SONY"
|
||||
|
||||
|
||||
def main():
|
||||
"""Find all photos with EXIF or XMP rating of 5 and mark them as favorites"""
|
||||
photosdb = osxphotos.PhotosDB()
|
||||
for photo in photosdb.photos():
|
||||
# extracting the rating data takes a while so
|
||||
# skip photos not taken with the camera we're looking for
|
||||
if photo.exif_info.camera_make != CAMERA_MAKE:
|
||||
continue
|
||||
|
||||
# Photos stores some data in photo.exif but the rating data must be extracted with exiftool
|
||||
if exif := photo.exiftool:
|
||||
exif_data = exif.asdict()
|
||||
# I think SONY uses XMP:Rating but also check EXIF:Rating
|
||||
xmp_rating = exif_data.get("XMP:Rating", 0)
|
||||
exif_rating = exif_data.get("EXIF:Rating", 0)
|
||||
rating = max(xmp_rating, exif_rating)
|
||||
if rating == 5:
|
||||
print(f"Marking {photo.original_filename} ({photo.uuid}) as favorite")
|
||||
photoscript.Photo(photo.uuid).favorite = True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -5,16 +5,20 @@
|
||||
"""
|
||||
|
||||
import pathlib
|
||||
from typing import List, Union
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import osxphotos
|
||||
from osxphotos import ExportOptions, PhotoInfo
|
||||
|
||||
|
||||
def example(photo: osxphotos.PhotoInfo, **kwargs) -> Union[List, str]:
|
||||
""" example function for {function} template; adds suffix of # if photo has adjustments and ! if photo is a favorite
|
||||
def example(
|
||||
photo: PhotoInfo, options: ExportOptions, args: Optional[str] = None, **kwargs
|
||||
) -> Union[List, str]:
|
||||
"""example function for {function} template; adds suffix of # if photo has adjustments and ! if photo is a favorite
|
||||
|
||||
Args:
|
||||
photo: osxphotos.PhotoInfo object
|
||||
options: osxphotos.ExportOptions object
|
||||
args: optional str of arguments passed to template function
|
||||
**kwargs: not currently used, placeholder to keep functions compatible with possible changes to {function}
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -2,11 +2,11 @@ import logging
|
||||
|
||||
from ._constants import AlbumSortOrder
|
||||
from ._version import __version__
|
||||
from .albuminfo import AlbumInfo, ImportInfo, ProjectInfo, FolderInfo
|
||||
from .albuminfo import AlbumInfo, FolderInfo, ImportInfo, ProjectInfo
|
||||
from .debug import is_debug, set_debug
|
||||
from .exifinfo import ExifInfo
|
||||
from .exiftool import ExifTool
|
||||
from .export_db import ExportDB
|
||||
from .export_db import ExportDB, ExportDBTemp
|
||||
from .fileutil import FileUtil, FileUtilNoOp
|
||||
from .momentinfo import MomentInfo
|
||||
from .personinfo import PersonInfo
|
||||
|
||||
@@ -44,10 +44,8 @@ _PHOTOS_5_VERSION = "5000" # I've seen both 5001 and 6000. 6000 is most common
|
||||
# Ranges for model version by Photos version
|
||||
_PHOTOS_5_MODEL_VERSION = [13000, 13999]
|
||||
_PHOTOS_6_MODEL_VERSION = [14000, 14999]
|
||||
_PHOTOS_7_MODEL_VERSION = [
|
||||
15000,
|
||||
15999,
|
||||
] # Monterey developer preview is 15134, 12.1 is 15331
|
||||
_PHOTOS_7_MODEL_VERSION = [15000, 15999] # Dev preview: 15134, 12.1: 15331
|
||||
_PHOTOS_8_MODEL_VERSION = [16000, 16999] # Ventura dev preview: 16119
|
||||
|
||||
# some table names differ between Photos 5 and Photos 6
|
||||
_DB_TABLE_NAMES = {
|
||||
@@ -87,6 +85,18 @@ _DB_TABLE_NAMES = {
|
||||
"ASSET_ALBUM_TABLE": "Z_27ASSETS",
|
||||
"HDR_TYPE": "ZHDRTYPE",
|
||||
},
|
||||
8: {
|
||||
"ASSET": "ZASSET",
|
||||
"KEYWORD_JOIN": "Z_1KEYWORDS.Z_40KEYWORDS",
|
||||
"ALBUM_JOIN": "Z_28ASSETS.Z_3ASSETS",
|
||||
"ALBUM_SORT_ORDER": "Z_28ASSETS.Z_FOK_3ASSETS",
|
||||
"IMPORT_FOK": "null",
|
||||
"DEPTH_STATE": "ZASSET.ZDEPTHTYPE",
|
||||
"UTI_ORIGINAL": "ZINTERNALRESOURCE.ZCOMPACTUTI",
|
||||
"ASSET_ALBUM_JOIN": "Z_28ASSETS.Z_28ALBUMS",
|
||||
"ASSET_ALBUM_TABLE": "Z_28ASSETS",
|
||||
"HDR_TYPE": "ZHDRTYPE",
|
||||
},
|
||||
}
|
||||
|
||||
# which version operating systems have been tested
|
||||
@@ -107,6 +117,7 @@ _TESTED_OS_VERSIONS = [
|
||||
("12", "1"),
|
||||
("12", "2"),
|
||||
("12", "3"),
|
||||
("12", "4"),
|
||||
]
|
||||
|
||||
# Photos 5 has persons who are empty string if unidentified face
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.49.2"
|
||||
__version__ = "0.50.4"
|
||||
|
||||
@@ -59,6 +59,7 @@ from .keywords import keywords
|
||||
from .labels import labels
|
||||
from .list import _list_libraries, list_libraries
|
||||
from .persons import persons
|
||||
from .photo_inspect import photo_inspect
|
||||
from .places import places
|
||||
from .query import query
|
||||
from .repl import repl
|
||||
@@ -88,6 +89,7 @@ __all__ = [
|
||||
"list_libraries",
|
||||
"load_uuid_from_file",
|
||||
"persons",
|
||||
"photo_inspect",
|
||||
"places",
|
||||
"query",
|
||||
"repl",
|
||||
|
||||
@@ -21,6 +21,7 @@ from .keywords import keywords
|
||||
from .labels import labels
|
||||
from .list import list_libraries
|
||||
from .persons import persons
|
||||
from .photo_inspect import photo_inspect
|
||||
from .places import places
|
||||
from .query import query
|
||||
from .repl import repl
|
||||
@@ -79,6 +80,7 @@ for command in [
|
||||
labels,
|
||||
list_libraries,
|
||||
persons,
|
||||
photo_inspect,
|
||||
places,
|
||||
query,
|
||||
repl,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""exiftool command for osxphotos CLI to update an previous export with exiftool metadata"""
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from typing import Callable
|
||||
@@ -18,7 +19,6 @@ from osxphotos.utils import pluralize
|
||||
|
||||
from .click_rich_echo import (
|
||||
rich_click_echo,
|
||||
rich_echo,
|
||||
rich_echo_error,
|
||||
set_rich_console,
|
||||
set_rich_theme,
|
||||
@@ -343,6 +343,8 @@ def process_files(
|
||||
total = len(files)
|
||||
count = 1
|
||||
all_results = ExportResults()
|
||||
# process files that are hardlinked? Requires user confirmation
|
||||
hardlink_ok = False
|
||||
with rich_progress(console=get_verbose_console(), mock=options.verbose) as progress:
|
||||
task = progress.add_task("Processing files", total=total)
|
||||
for uuid, file in files:
|
||||
@@ -350,7 +352,14 @@ def process_files(
|
||||
verbose(f"Skipping missing file [filepath]{file}[/]")
|
||||
report_writer.write(ExportResults(missing=[file]))
|
||||
continue
|
||||
# zzz put in check for hardlink
|
||||
if not hardlink_ok and os.stat(file).st_nlink > 1:
|
||||
rich_click_echo(
|
||||
f"[warning]:warning-emoji: Warning: file [filepath]{file}[/] is hardlinked.\n"
|
||||
"You may be modifying linked original files in your Photos library. "
|
||||
"This is inadvisable.[/]",
|
||||
)
|
||||
click.confirm("Continue processing hardlinks?", abort=True)
|
||||
hardlink_ok = True
|
||||
verbose(f"Processing file [filepath]{file}[/] ([num]{count}/{total}[/num])")
|
||||
photo = photosdb.get_photo(uuid)
|
||||
export_options = ExportOptions(
|
||||
|
||||
@@ -1920,7 +1920,7 @@ def export_photo(
|
||||
original_filename = str(original_filename)
|
||||
|
||||
verbose_(
|
||||
f"Exporting [filename]{photo.original_filename}[/] ([filename]{photo.filename}[/]) as [filepath]{original_filename}[/] ([count]{photo_num}/{num_photos}[/])"
|
||||
f"Exporting [filename]{photo.original_filename}[/] ([filename]{photo.filename}[/]) ([count]{photo_num}/{num_photos}[/])"
|
||||
)
|
||||
|
||||
results += export_photo_to_directory(
|
||||
@@ -2034,7 +2034,7 @@ def export_photo(
|
||||
)
|
||||
|
||||
verbose_(
|
||||
f"Exporting edited version of [filename]{photo.original_filename}[/filename] ([filename]{photo.filename}[/filename]) as [filepath]{edited_filename}[/filepath]"
|
||||
f"Exporting edited version of [filename]{photo.original_filename}[/filename] ([filename]{photo.filename}[/filename])"
|
||||
)
|
||||
|
||||
results += export_photo_to_directory(
|
||||
@@ -2728,4 +2728,3 @@ def render_and_validate_report(report: str, exiftool_path: str, export_dir: str)
|
||||
sys.exit(1)
|
||||
|
||||
return report
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""exportdb command for osxphotos CLI"""
|
||||
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
@@ -22,6 +23,7 @@ from osxphotos.export_db_utils import (
|
||||
export_db_update_signatures,
|
||||
export_db_vacuum,
|
||||
)
|
||||
from osxphotos.utils import pluralize
|
||||
|
||||
from .export import render_and_validate_report
|
||||
from .param_types import TemplateString
|
||||
@@ -63,6 +65,33 @@ from .verbose import verbose_print
|
||||
nargs=1,
|
||||
help="Print information about FILE_PATH contained in the database.",
|
||||
)
|
||||
@click.option(
|
||||
"--uuid-files",
|
||||
metavar="UUID",
|
||||
nargs=1,
|
||||
help="List exported files associated with UUID.",
|
||||
)
|
||||
@click.option(
|
||||
"--uuid-info",
|
||||
metavar="UUID",
|
||||
nargs=1,
|
||||
help="Print information about UUID contained in the database.",
|
||||
)
|
||||
@click.option(
|
||||
"--delete-uuid",
|
||||
metavar="UUID",
|
||||
nargs=1,
|
||||
multiple=True,
|
||||
help="Delete all data associated with UUID from the database.",
|
||||
)
|
||||
@click.option(
|
||||
"--delete-file",
|
||||
metavar="FILE_PATH",
|
||||
nargs=1,
|
||||
multiple=True,
|
||||
help="Delete all data associated with FILE_PATH from the database; "
|
||||
"does not delete the actual exported file if it exists, only the data in the database.",
|
||||
)
|
||||
@click.option(
|
||||
"--report",
|
||||
metavar="REPORT_FILE RUN_ID",
|
||||
@@ -110,22 +139,26 @@ from .verbose import verbose_print
|
||||
)
|
||||
@click.argument("export_db", metavar="EXPORT_DATABASE", type=click.Path(exists=True))
|
||||
def exportdb(
|
||||
version,
|
||||
vacuum,
|
||||
check_signatures,
|
||||
update_signatures,
|
||||
touch_file,
|
||||
last_run,
|
||||
save_config,
|
||||
info,
|
||||
report,
|
||||
migrate,
|
||||
sql,
|
||||
export_dir,
|
||||
append,
|
||||
verbose,
|
||||
check_signatures,
|
||||
dry_run,
|
||||
export_db,
|
||||
export_dir,
|
||||
info,
|
||||
last_run,
|
||||
migrate,
|
||||
report,
|
||||
save_config,
|
||||
sql,
|
||||
touch_file,
|
||||
update_signatures,
|
||||
uuid_files,
|
||||
uuid_info,
|
||||
delete_uuid,
|
||||
delete_file,
|
||||
vacuum,
|
||||
verbose,
|
||||
version,
|
||||
):
|
||||
"""Utilities for working with the osxphotos export database"""
|
||||
|
||||
@@ -163,6 +196,8 @@ def exportdb(
|
||||
sql,
|
||||
touch_file,
|
||||
update_signatures,
|
||||
uuid_files,
|
||||
uuid_info,
|
||||
vacuum,
|
||||
version,
|
||||
]
|
||||
@@ -273,6 +308,55 @@ def exportdb(
|
||||
print(f"[red]File '{info}' not found in export database[/red]")
|
||||
sys.exit(0)
|
||||
|
||||
if uuid_info:
|
||||
# get photoinfo record for a uuid
|
||||
exportdb = ExportDB(export_db, export_dir)
|
||||
try:
|
||||
info_rec = exportdb.get_photoinfo_for_uuid(uuid_info)
|
||||
except Exception as e:
|
||||
print(f"[red]Error: {e}[/red]")
|
||||
sys.exit(1)
|
||||
else:
|
||||
if info_rec:
|
||||
print(json.dumps(json.loads(info_rec), sort_keys=True, indent=2))
|
||||
else:
|
||||
print(f"[red]UUID '{uuid_info}' not found in export database[/red]")
|
||||
sys.exit(0)
|
||||
|
||||
if uuid_files:
|
||||
# list files associated with a uuid
|
||||
exportdb = ExportDB(export_db, export_dir)
|
||||
try:
|
||||
file_list = exportdb.get_files_for_uuid(uuid_files)
|
||||
except Exception as e:
|
||||
print(f"[red]Error: {e}[/red]")
|
||||
sys.exit(1)
|
||||
else:
|
||||
if file_list:
|
||||
for f in file_list:
|
||||
print(f)
|
||||
else:
|
||||
print(f"[red]UUID '{uuid_files}' not found in export database[/red]")
|
||||
sys.exit(0)
|
||||
|
||||
if delete_uuid:
|
||||
# delete a uuid from the export database
|
||||
exportdb = ExportDB(export_db, export_dir)
|
||||
for uuid in delete_uuid:
|
||||
print(f"Deleting uuid {uuid} from database.")
|
||||
count = exportdb.delete_data_for_uuid(uuid)
|
||||
print(f"Deleted {count} {pluralize(count, 'record', 'records')}.")
|
||||
sys.exit(0)
|
||||
|
||||
if delete_file:
|
||||
# delete information associated with a file from the export database
|
||||
exportdb = ExportDB(export_db, export_dir)
|
||||
for filepath in delete_file:
|
||||
print(f"Deleting file {filepath} from database.")
|
||||
count = exportdb.delete_data_for_filepath(filepath)
|
||||
print(f"Deleted {count} {pluralize(count, 'record', 'records')}.")
|
||||
sys.exit(0)
|
||||
|
||||
if report:
|
||||
exportdb = ExportDB(export_db, export_dir)
|
||||
report_template, run_id = report
|
||||
|
||||
517
osxphotos/cli/photo_inspect.py
Normal file
@@ -0,0 +1,517 @@
|
||||
"""Inspect photos selected in Photos """
|
||||
|
||||
import functools
|
||||
import re
|
||||
from fractions import Fraction
|
||||
from multiprocessing import Process, Queue
|
||||
from queue import Empty
|
||||
from time import gmtime, sleep, strftime
|
||||
from typing import List, Optional, Tuple
|
||||
import pathlib
|
||||
|
||||
import bitmath
|
||||
import click
|
||||
from applescript import ScriptError
|
||||
from photoscript import PhotosLibrary
|
||||
from rich.console import Console
|
||||
from rich.layout import Layout
|
||||
from rich.live import Live
|
||||
from rich.panel import Panel
|
||||
|
||||
from osxphotos import PhotoInfo, PhotosDB
|
||||
from osxphotos._constants import _UNKNOWN_PERSON
|
||||
from osxphotos.rich_utils import add_rich_markup_tag
|
||||
from osxphotos.text_detection import detect_text as detect_text_in_photo
|
||||
from osxphotos.utils import dd_to_dms_str
|
||||
|
||||
from .color_themes import get_theme
|
||||
from .common import DB_OPTION, THEME_OPTION, get_photos_db
|
||||
|
||||
# global that tracks UUID being inspected
|
||||
CURRENT_UUID = None
|
||||
|
||||
# helpers for markup
|
||||
bold = add_rich_markup_tag("bold")
|
||||
dim = add_rich_markup_tag("dim")
|
||||
|
||||
|
||||
def extract_uuid(text: str) -> str:
|
||||
"""Extract a UUID from a string"""
|
||||
if match := re.search(
|
||||
r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})",
|
||||
text,
|
||||
):
|
||||
return match[1]
|
||||
return None
|
||||
|
||||
|
||||
def trim(text: str, pad: str = "") -> str:
|
||||
"""Truncate a string to a fit in console, - len(pad) - 4; also removes new lines"""
|
||||
width = Console().width - len(pad) - 4
|
||||
text = text.replace("\n", " ")
|
||||
return text if len(text) <= width else f"{text[: width- 3]}..."
|
||||
|
||||
|
||||
def inspect_photo(
|
||||
photo: PhotoInfo,
|
||||
detected_text: Optional[str] = None,
|
||||
templates: Optional[List[str]] = None,
|
||||
) -> str:
|
||||
"""Get info about an osxphotos PhotoInfo object formatted for printing"""
|
||||
|
||||
if templates:
|
||||
return inspect_photo_templates(photo, templates)
|
||||
|
||||
properties = [
|
||||
bold("Filename: ") + f"[filename]{photo.original_filename}[/]",
|
||||
bold("Type: ") + get_photo_type(photo),
|
||||
bold("UUID: ") + f"[uuid]{photo.uuid}[/]",
|
||||
bold("Date: ") + f"[time]{photo.date.isoformat()}[/]",
|
||||
bold("Date added: ") + f"[time]{photo.date_added.isoformat()}[/]",
|
||||
]
|
||||
if photo.date_modified:
|
||||
properties.append(
|
||||
bold("Date modified: ") + f"[time]{photo.date_modified.isoformat()}[/]"
|
||||
)
|
||||
|
||||
if photo.intrash and photo.date_trashed:
|
||||
properties.append(
|
||||
bold("Date deleted: ") + f"[time]{photo.date_trashed.isoformat()}[/]"
|
||||
)
|
||||
|
||||
if photo.import_info and photo.import_info.creation_date:
|
||||
properties.append(
|
||||
bold("Date imported: ")
|
||||
+ f"[time]{photo.import_info.creation_date.isoformat()}[/]"
|
||||
)
|
||||
|
||||
properties.extend(
|
||||
[
|
||||
bold("Dimensions: ")
|
||||
+ f"[num]{photo.width}[/] x [num]{photo.height}[/] "
|
||||
+ bold("Orientation: ")
|
||||
+ f"[num]{photo.orientation}[/]",
|
||||
bold("File size: ")
|
||||
+ f"[num]{float(bitmath.Byte(photo.original_filesize).to_MB()):.2f} MB[/]",
|
||||
bold("Title: ") + f"{photo.title or '-'}",
|
||||
bold("Description: ")
|
||||
+ f"{trim(photo.description or '-', 'Description: ')}",
|
||||
bold("Edited: ")
|
||||
+ f"{'✔' if photo.hasadjustments else '-'} "
|
||||
+ bold("External edits: ")
|
||||
+ f"{'✔' if photo.external_edit else '-'}",
|
||||
bold("Keywords: ") + f"{', '.join(photo.keywords) or '-'}",
|
||||
bold("Persons: ")
|
||||
+ f"{', '.join(p for p in photo.persons if p != _UNKNOWN_PERSON) or '-'}",
|
||||
bold("Location: ")
|
||||
+ f"{', '.join(dd_to_dms_str(*photo.location)) if photo.location[0] else '-'}",
|
||||
bold("Place: ") + f"{photo.place.name if photo.place else '-'}",
|
||||
bold("Categories: ") + f"{', '.join(photo.labels) or '-'}",
|
||||
]
|
||||
)
|
||||
properties.append(format_flags(photo))
|
||||
properties.append(format_albums(photo))
|
||||
|
||||
if photo.project_info:
|
||||
properties.append(
|
||||
bold("Projects: ")
|
||||
+ f"{', '.join(p.title for p in photo.project_info) or '-'}"
|
||||
)
|
||||
|
||||
if photo.moment_info:
|
||||
properties.append(bold("Moment: ") + f"{photo.moment_info.title or '-'}")
|
||||
|
||||
if photo.comments:
|
||||
comments = [f"{c.user}: {c.text}" for c in photo.comments]
|
||||
properties.append(
|
||||
bold("Comments: ") + trim(f"{', '.join(comments)}", "Comments: ")
|
||||
)
|
||||
|
||||
if photo.likes:
|
||||
properties.append(
|
||||
bold("Likes: ")
|
||||
+ trim(f"{', '.join(l.user for l in photo.likes)}", "Likes: ")
|
||||
)
|
||||
|
||||
properties.append(format_exif_info(photo))
|
||||
properties.append(format_score_info(photo))
|
||||
|
||||
if detected_text:
|
||||
# have detected text for this photo
|
||||
properties.append(
|
||||
bold("Detected text: ") + trim(detected_text, "Detected text: ")
|
||||
)
|
||||
|
||||
properties.append(format_paths(photo))
|
||||
|
||||
return "\n".join(properties)
|
||||
|
||||
|
||||
def inspect_photo_templates(
|
||||
photo: PhotoInfo, templates: Optional[List[str]] = None
|
||||
) -> str:
|
||||
"""Render and display photo templates"""
|
||||
properties = [
|
||||
bold("Filename: ") + f"[filename]{photo.original_filename}[/]",
|
||||
bold("Type: ") + get_photo_type(photo),
|
||||
bold("UUID: ") + f"[uuid]{photo.uuid}[/]",
|
||||
]
|
||||
properties.append(bold("Templates: "))
|
||||
properties.append(format_templates(photo, templates))
|
||||
|
||||
return "\n".join(properties)
|
||||
|
||||
|
||||
def format_templates(photo: PhotoInfo, templates: List[str]) -> str:
|
||||
"""Format templates for a photo"""
|
||||
formatted_templates = []
|
||||
for template in templates:
|
||||
template_str, _ = photo.render_template(template)
|
||||
formatted_templates.append((template, template_str))
|
||||
return "\n".join(f"{t[0]} = {t[1]}" for t in formatted_templates)
|
||||
|
||||
|
||||
def format_score_info(photo: PhotoInfo) -> str:
|
||||
"""Format score_info"""
|
||||
score_str = bold("Score: ")
|
||||
if photo.score:
|
||||
score_str += f"[num]{photo.score.overall}[/]" if photo.score else "-"
|
||||
return score_str
|
||||
|
||||
|
||||
def format_flags(photo: PhotoInfo) -> str:
|
||||
"""Format special properties"""
|
||||
flag_str = bold("Flags: ")
|
||||
flags = []
|
||||
if photo.favorite:
|
||||
flags.append("favorite")
|
||||
if photo.visible:
|
||||
flags.append("visible")
|
||||
if photo.hidden:
|
||||
flags.append("hidden")
|
||||
if photo.ismissing:
|
||||
flags.append("missing")
|
||||
if photo.intrash:
|
||||
flags.append("in trash")
|
||||
if photo.iscloudasset:
|
||||
flags.append("cloud asset")
|
||||
if photo.incloud:
|
||||
flags.append("in cloud")
|
||||
if photo.shared:
|
||||
flags.append("shared")
|
||||
|
||||
flag_str += f"{', '.join(flags) or '-'}"
|
||||
return flag_str
|
||||
|
||||
|
||||
def format_albums(photo: PhotoInfo) -> str:
|
||||
"""Format albums for inspect_photo"""
|
||||
album_str = bold("Albums: ")
|
||||
album_names = []
|
||||
for album in photo.album_info:
|
||||
if album.folder_names:
|
||||
folder_str = "/".join(album.folder_names)
|
||||
album_names.append(f"{folder_str}/{album.title}")
|
||||
else:
|
||||
album_names.append(album.title)
|
||||
album_str += f"{', '.join(album_names) or '-'}"
|
||||
return album_str
|
||||
|
||||
|
||||
def format_path_link(path: str) -> str:
|
||||
"""Format a path as URI for display in terminal"""
|
||||
return f"[link={pathlib.Path(path).as_uri()}]{path}[/link]"
|
||||
|
||||
|
||||
def format_paths(photo: PhotoInfo) -> str:
|
||||
"""format photo paths for inspect_photo"""
|
||||
path_str = bold("Path original: ")
|
||||
path_str += f"[filepath]{format_path_link(photo.path)}[/]" if photo.path else "-"
|
||||
if photo.path_edited:
|
||||
path_str += "\n"
|
||||
path_str += bold("Path edited: ")
|
||||
path_str += f"[filepath]{format_path_link(photo.path_edited)}[/]"
|
||||
if photo.path_raw:
|
||||
path_str += "\n"
|
||||
path_str += bold("Path raw: ")
|
||||
path_str += f"[filepath]{format_path_link(photo.path_raw)}[/]"
|
||||
if photo.path_derivatives:
|
||||
path_str += "\n"
|
||||
path_str += bold("Path preview: ")
|
||||
path_str += f"[filepath]{format_path_link(photo.path_derivatives[0])}[/]"
|
||||
return path_str
|
||||
|
||||
|
||||
def format_exif_info(photo: PhotoInfo) -> str:
|
||||
"""Format exif_info for inspect_photo"""
|
||||
exif_str = bold("EXIF: ")
|
||||
exif = photo.exif_info
|
||||
if not exif:
|
||||
return f"{exif_str}-"
|
||||
|
||||
if exif.camera_make:
|
||||
exif_str += f"{exif.camera_make} "
|
||||
if exif.camera_model:
|
||||
exif_str += f"{exif.camera_model} "
|
||||
if exif.focal_length:
|
||||
exif_str += f"{exif.focal_length:.2f}mm "
|
||||
if exif.iso:
|
||||
exif_str += f"ISO {exif.iso} "
|
||||
if exif.flash_fired:
|
||||
exif_str += "Flash "
|
||||
if exif.exposure_bias is not None:
|
||||
exif_str += (
|
||||
f"{int(exif.exposure_bias)} ev "
|
||||
if exif.exposure_bias == int(exif.exposure_bias)
|
||||
else f"{exif.exposure_bias} ev "
|
||||
)
|
||||
if exif.aperture:
|
||||
exif_str += (
|
||||
f"ƒ{int(exif.aperture)} "
|
||||
if exif.aperture == int(exif.aperture)
|
||||
else f"ƒ{exif.aperture:.1f} "
|
||||
)
|
||||
if exif.shutter_speed:
|
||||
exif_str += f"{Fraction(exif.shutter_speed).limit_denominator(100_000)}s "
|
||||
if exif.bit_rate:
|
||||
exif_str += f"{exif.bit_rate} bit rate"
|
||||
if exif.sample_rate:
|
||||
exif_str += f"{exif.sample_rate} sample rate"
|
||||
if exif.fps:
|
||||
exif_str += f"{exif.fps or 0:.1f}FPS "
|
||||
if exif.duration:
|
||||
exif_str += f"{strftime('%H:%M:%S', gmtime(exif.duration or 0))} "
|
||||
if exif.codec:
|
||||
exif_str += f"{exif.codec}"
|
||||
if exif.track_format:
|
||||
exif_str += f"{exif.track_format}"
|
||||
return exif_str
|
||||
|
||||
|
||||
def get_photo_type(photo: PhotoInfo) -> str:
|
||||
"""Return a string describing the type of photo"""
|
||||
photo_type = "video" if photo.ismovie else "photo"
|
||||
if photo.has_raw:
|
||||
photo_type += " RAW+JPEG"
|
||||
if photo.israw:
|
||||
photo_type += " RAW"
|
||||
if photo.burst:
|
||||
photo_type += " burst"
|
||||
if photo.live_photo:
|
||||
photo_type += " live"
|
||||
if photo.selfie:
|
||||
photo_type += " selfie"
|
||||
if photo.panorama:
|
||||
photo_type += " panorama"
|
||||
if photo.hdr:
|
||||
photo_type += " HDR"
|
||||
if photo.screenshot:
|
||||
photo_type += " screenshot"
|
||||
if photo.slow_mo:
|
||||
photo_type += " slow-mo"
|
||||
if photo.time_lapse:
|
||||
photo_type += " time-lapse"
|
||||
if photo.portrait:
|
||||
photo_type += " portrait"
|
||||
return photo_type
|
||||
|
||||
|
||||
def start_text_detection(photo: PhotoInfo) -> Tuple[Process, Queue]:
|
||||
"""Start text detection process for a photo"""
|
||||
path_preview = photo.path_derivatives[0] if photo.path_derivatives else None
|
||||
path = photo.path_edited or photo.path or path_preview
|
||||
if not path:
|
||||
raise ValueError("No path to photo")
|
||||
queue = Queue()
|
||||
p = Process(
|
||||
target=_get_detected_text,
|
||||
args=(photo.uuid, path, photo.orientation, queue),
|
||||
)
|
||||
p.start()
|
||||
return (p, queue)
|
||||
|
||||
|
||||
def _get_detected_text(uuid: str, path: str, orientation: int, queue: Queue) -> None:
|
||||
"""Called by start_text_detection to run text detection in separate process"""
|
||||
try:
|
||||
if text := detect_text_in_photo(path, orientation):
|
||||
queue.put([uuid, " ".join(t[0] for t in text if t[1] > 0.5)])
|
||||
except Exception as e:
|
||||
queue.put([None, str(e)])
|
||||
|
||||
|
||||
def get_uuid_for_photos_selection() -> List[str]:
|
||||
"""Get the uuid for the first photo selected in Photos
|
||||
|
||||
Returns: tuple of (uuid, total_selected_photos)"""
|
||||
photoslib = PhotosLibrary()
|
||||
try:
|
||||
if photos := photoslib.selection:
|
||||
return photos[0].uuid, len(photos)
|
||||
except (ValueError, ScriptError) as e:
|
||||
if uuid := extract_uuid(str(e)):
|
||||
return uuid, 1
|
||||
else:
|
||||
raise e
|
||||
return None, 0
|
||||
|
||||
|
||||
def make_layout() -> Layout:
|
||||
"""Define the layout."""
|
||||
layout = Layout(name="root")
|
||||
|
||||
layout.split(
|
||||
Layout(name="main", ratio=1),
|
||||
Layout(name="status", size=1),
|
||||
Layout(name="footer", size=1),
|
||||
)
|
||||
return layout
|
||||
|
||||
|
||||
@click.command(name="inspect")
|
||||
@click.option("--detect-text", "-t", is_flag=True, help="Detect text in photos")
|
||||
@click.option(
|
||||
"--template",
|
||||
"-T",
|
||||
metavar="TEMPLATE",
|
||||
multiple=True,
|
||||
help="Template string to render for each photo using template preview mode. "
|
||||
"Useful for testing templates for export; may be repeated to test multiple templates. "
|
||||
"If --template/-T is used, other inspection data will not be displayed. ",
|
||||
)
|
||||
@THEME_OPTION
|
||||
@DB_OPTION
|
||||
def photo_inspect(db, theme, detect_text, template):
|
||||
"""Interactively inspect photos selected in Photos.
|
||||
|
||||
Open Photos then run `osxphotos inspect` in the terminal.
|
||||
As you select a photo in Photos, inspect will display metadata about the photo.
|
||||
Press Ctrl+C to exit when done.
|
||||
Works best with a modern terminal like iTerm2 or Kitty.
|
||||
"""
|
||||
db = get_photos_db(db)
|
||||
if not db:
|
||||
raise click.UsageError(
|
||||
"Did not locate Photos database. Try running with --db option."
|
||||
)
|
||||
|
||||
theme = get_theme(theme)
|
||||
console = Console(theme=theme)
|
||||
|
||||
layout = make_layout()
|
||||
layout["footer"].update("Press Ctrl+C to quit")
|
||||
layout["main"].update(
|
||||
Panel("Loading Photos database, hold on...", title="Loading...")
|
||||
)
|
||||
|
||||
def loading_status(status: str):
|
||||
layout["status"].update(f"Loading database: {status}")
|
||||
|
||||
def update_status(status: str):
|
||||
layout["status"].update(status)
|
||||
|
||||
def update_detected_text(photo: PhotoInfo, uuid: str, text: str):
|
||||
global CURRENT_UUID
|
||||
if uuid == CURRENT_UUID:
|
||||
layout["main"].update(
|
||||
Panel(
|
||||
inspect_photo(photo, detected_text=text),
|
||||
title=photo.title or photo.original_filename,
|
||||
)
|
||||
)
|
||||
|
||||
with Live(layout, console=console, refresh_per_second=10, screen=True):
|
||||
photosdb = PhotosDB(dbfile=db, verbose=loading_status)
|
||||
layout["main"].update(
|
||||
Panel("Select a photo in Photos to inspect", title="Select a photo")
|
||||
)
|
||||
processes = []
|
||||
global CURRENT_UUID
|
||||
detected_text_cache = {}
|
||||
last_uuid = None
|
||||
uuid = None
|
||||
total = 0
|
||||
while True:
|
||||
try:
|
||||
uuid, total = get_uuid_for_photos_selection()
|
||||
except Exception as e:
|
||||
layout["main"].update(Panel(f"Error: {e}", title="Error"))
|
||||
except KeyboardInterrupt:
|
||||
# allow Ctrl+C to quit
|
||||
break
|
||||
finally:
|
||||
if uuid and uuid != last_uuid:
|
||||
if total > 1:
|
||||
update_status(
|
||||
f"{total} photos selected; inspecting uuid={uuid}"
|
||||
)
|
||||
else:
|
||||
update_status(f"Inspecting uuid={uuid}")
|
||||
CURRENT_UUID = uuid
|
||||
if photo := photosdb.get_photo(uuid):
|
||||
layout["main"].update(
|
||||
Panel(
|
||||
inspect_photo(
|
||||
photo,
|
||||
detected_text=detected_text_cache.get(uuid, None),
|
||||
templates=template,
|
||||
),
|
||||
title=photo.title or photo.original_filename,
|
||||
)
|
||||
)
|
||||
|
||||
# start text detection if requested (but not if in template preview mode)
|
||||
if (
|
||||
detect_text
|
||||
and not template
|
||||
and photo.isphoto
|
||||
and (
|
||||
photo.path
|
||||
or photo.path_edited
|
||||
or photo.path_derivatives
|
||||
)
|
||||
and uuid not in detected_text_cache
|
||||
):
|
||||
# can only detect text on photos if on disk
|
||||
# start text detection in separate process as it can take a few seconds
|
||||
update_detected_text_callback = functools.partial(
|
||||
update_detected_text, photo
|
||||
)
|
||||
process, queue = start_text_detection(photo)
|
||||
processes.append(
|
||||
[True, process, queue, update_detected_text_callback]
|
||||
)
|
||||
else:
|
||||
layout["main"].update(
|
||||
Panel(
|
||||
f"No photo found in database for uuid={uuid}",
|
||||
title="Error",
|
||||
)
|
||||
)
|
||||
last_uuid = uuid
|
||||
if not uuid:
|
||||
last_uuid = None
|
||||
layout["main"].update(
|
||||
Panel("Select a photo in Photos to inspect", title="Select a photo")
|
||||
)
|
||||
update_status("Select a photo in Photos to inspect")
|
||||
if detect_text:
|
||||
# check on text detection processes
|
||||
for _, values in enumerate(processes):
|
||||
alive, process, queue, update_detected_text_callback = values
|
||||
if alive:
|
||||
# process hasn't been marked as dead yet
|
||||
try:
|
||||
uuid, text = queue.get(False)
|
||||
update_detected_text_callback(uuid, text)
|
||||
detected_text_cache[uuid] = text
|
||||
process.join()
|
||||
# set alive = False
|
||||
values[0] = False
|
||||
except Empty:
|
||||
if not process.is_alive():
|
||||
# process has finished, nothing in the queue
|
||||
process.join()
|
||||
# set alive = False
|
||||
values[0] = False
|
||||
sleep(0.100)
|
||||
@@ -6,8 +6,10 @@ import gzip
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import pickle
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
import time
|
||||
@@ -15,9 +17,9 @@ from contextlib import suppress
|
||||
from io import StringIO
|
||||
from sqlite3 import Error
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Any, Optional, Tuple, Union
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
|
||||
from tenacity import retry, stop_after_attempt
|
||||
from tenacity import retry, retry_if_not_exception_type, stop_after_attempt
|
||||
|
||||
from ._constants import OSXPHOTOS_EXPORT_DB
|
||||
from ._version import __version__
|
||||
@@ -30,11 +32,11 @@ __all__ = [
|
||||
"ExportDBTemp",
|
||||
]
|
||||
|
||||
OSXPHOTOS_EXPORTDB_VERSION = "7.0"
|
||||
OSXPHOTOS_EXPORTDB_VERSION = "7.1"
|
||||
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
|
||||
MAX_RETRY_ATTEMPTS = 3
|
||||
|
||||
# maximum number of export results rows to save
|
||||
MAX_EXPORT_RESULTS_DATA_ROWS = 10
|
||||
@@ -99,6 +101,7 @@ class ExportDB:
|
||||
"""returns path to export directory"""
|
||||
return self._path
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def get_file_record(self, filename: Union[pathlib.Path, str]) -> "ExportRecord":
|
||||
"""get info for filename and uuid
|
||||
|
||||
@@ -116,6 +119,10 @@ class ExportDB:
|
||||
return ExportRecord(conn, filename_normalized)
|
||||
return None
|
||||
|
||||
@retry(
|
||||
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
||||
retry=retry_if_not_exception_type(sqlite3.IntegrityError),
|
||||
)
|
||||
def create_file_record(
|
||||
self, filename: Union[pathlib.Path, str], uuid: str
|
||||
) -> "ExportRecord":
|
||||
@@ -134,6 +141,10 @@ class ExportDB:
|
||||
conn.commit()
|
||||
return ExportRecord(conn, filename_normalized)
|
||||
|
||||
@retry(
|
||||
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
||||
retry=retry_if_not_exception_type(sqlite3.IntegrityError),
|
||||
)
|
||||
def create_or_get_file_record(
|
||||
self, filename: Union[pathlib.Path, str], uuid: str
|
||||
) -> "ExportRecord":
|
||||
@@ -152,101 +163,137 @@ class ExportDB:
|
||||
conn.commit()
|
||||
return ExportRecord(conn, filename_normalized)
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def get_uuid_for_file(self, filename):
|
||||
"""query database for filename and return UUID
|
||||
returns None if filename not found in database
|
||||
"""
|
||||
filepath_normalized = self._normalize_filepath_relative(filename)
|
||||
conn = self._conn
|
||||
try:
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"SELECT uuid FROM export_data WHERE filepath_normalized = ?",
|
||||
(filepath_normalized,),
|
||||
)
|
||||
results = c.fetchone()
|
||||
uuid = results[0] if results else None
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
uuid = None
|
||||
return uuid
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"SELECT uuid FROM export_data WHERE filepath_normalized = ?",
|
||||
(filepath_normalized,),
|
||||
)
|
||||
results = c.fetchone()
|
||||
return results[0] if results else None
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def get_files_for_uuid(self, uuid: str) -> List:
|
||||
"""query database for UUID and return list of files associated with UUID or empty list"""
|
||||
conn = self._conn
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"SELECT filepath FROM export_data WHERE uuid = ?",
|
||||
(uuid,),
|
||||
)
|
||||
results = c.fetchall()
|
||||
return [os.path.join(self.export_dir, r[0]) for r in results]
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def get_photoinfo_for_uuid(self, uuid):
|
||||
"""returns the photoinfo JSON struct for a UUID"""
|
||||
conn = self._conn
|
||||
try:
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT photoinfo FROM photoinfo WHERE uuid = ?", (uuid,))
|
||||
results = c.fetchone()
|
||||
info = results[0] if results else None
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
info = None
|
||||
|
||||
return info
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT photoinfo FROM photoinfo WHERE uuid = ?", (uuid,))
|
||||
results = c.fetchone()
|
||||
return results[0] if results else None
|
||||
|
||||
@retry(
|
||||
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
||||
retry=retry_if_not_exception_type(sqlite3.IntegrityError),
|
||||
)
|
||||
def set_photoinfo_for_uuid(self, uuid, info):
|
||||
"""sets the photoinfo JSON struct for a UUID"""
|
||||
conn = self._conn
|
||||
try:
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"INSERT OR REPLACE INTO photoinfo(uuid, photoinfo) VALUES (?, ?);",
|
||||
(uuid, info),
|
||||
)
|
||||
conn.commit()
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"INSERT OR REPLACE INTO photoinfo(uuid, photoinfo) VALUES (?, ?);",
|
||||
(uuid, info),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def get_target_for_file(
|
||||
self, uuid: str, filename: Union[str, pathlib.Path]
|
||||
) -> Optional[str]:
|
||||
"""query database for file matching file name and return the matching filename if there is one;
|
||||
otherwise return None; looks for file.ext, file (1).ext, file (2).ext and so on to find the
|
||||
actual target name that was used to export filename
|
||||
|
||||
Returns: the matching filename or None if no match found
|
||||
"""
|
||||
conn = self._conn
|
||||
c = conn.cursor()
|
||||
filepath_normalized = self._normalize_filepath_relative(filename)
|
||||
filepath_stem = os.path.splitext(filepath_normalized)[0]
|
||||
c.execute(
|
||||
"SELECT uuid, filepath, filepath_normalized FROM export_data WHERE uuid = ? AND filepath_normalized LIKE ?",
|
||||
(
|
||||
uuid,
|
||||
f"{filepath_stem}%",
|
||||
),
|
||||
)
|
||||
results = c.fetchall()
|
||||
|
||||
for result in results:
|
||||
filepath_normalized = os.path.splitext(result[2])[0]
|
||||
if re.match(
|
||||
re.escape(filepath_stem) + r"(\s\(\d+\))?$", filepath_normalized
|
||||
):
|
||||
return os.path.join(self.export_dir, result[1])
|
||||
|
||||
return None
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def get_previous_uuids(self):
|
||||
"""returns list of UUIDs of previously exported photos found in export database"""
|
||||
conn = self._conn
|
||||
previous_uuids = []
|
||||
try:
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT DISTINCT uuid FROM export_data")
|
||||
results = c.fetchall()
|
||||
previous_uuids = [row[0] for row in results]
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
return previous_uuids
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT DISTINCT uuid FROM export_data")
|
||||
results = c.fetchall()
|
||||
return [row[0] for row in results]
|
||||
|
||||
@retry(
|
||||
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
||||
retry=retry_if_not_exception_type(sqlite3.IntegrityError),
|
||||
)
|
||||
def set_config(self, config_data):
|
||||
"""set config in the database"""
|
||||
conn = self._conn
|
||||
try:
|
||||
dt = datetime.datetime.now().isoformat()
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"INSERT OR REPLACE INTO config(datetime, config) VALUES (?, ?);",
|
||||
(dt, config_data),
|
||||
)
|
||||
conn.commit()
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
dt = datetime.datetime.now().isoformat()
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"INSERT OR REPLACE INTO config(datetime, config) VALUES (?, ?);",
|
||||
(dt, config_data),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
@retry(
|
||||
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
||||
retry=retry_if_not_exception_type(sqlite3.IntegrityError),
|
||||
)
|
||||
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)
|
||||
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()
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def get_export_results(self, run: int = 0):
|
||||
"""Retrieve export results from database
|
||||
|
||||
@@ -262,47 +309,67 @@ class ExportDB:
|
||||
run = -run
|
||||
|
||||
conn = self._conn
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"""
|
||||
SELECT export_results
|
||||
FROM export_results_data
|
||||
ORDER BY datetime DESC
|
||||
""",
|
||||
)
|
||||
rows = c.fetchall()
|
||||
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)
|
||||
data = rows[run][0]
|
||||
results = unzip_and_unpickle(data) if data else None
|
||||
except IndexError:
|
||||
results = None
|
||||
return results
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def get_exported_files(self):
|
||||
"""Returns tuple of (uuid, filepath) for all paths of all exported files tracked in the database"""
|
||||
conn = self._conn
|
||||
try:
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT uuid, filepath FROM export_data")
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
return
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT uuid, filepath FROM export_data")
|
||||
|
||||
while row := c.fetchone():
|
||||
yield row[0], os.path.join(self.export_dir, row[1])
|
||||
return
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def delete_data_for_uuid(self, uuid):
|
||||
"""Delete all exportdb data for given UUID"""
|
||||
conn = self._conn
|
||||
c = conn.cursor()
|
||||
count = 0
|
||||
c.execute("DELETE FROM export_data WHERE uuid = ?;", (uuid,))
|
||||
count += c.execute("SELECT CHANGES();").fetchone()[0]
|
||||
c.execute("DELETE FROM photoinfo WHERE uuid = ?;", (uuid,))
|
||||
count += c.execute("SELECT CHANGES();").fetchone()[0]
|
||||
conn.commit()
|
||||
return count
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def delete_data_for_filepath(self, filepath):
|
||||
"""Delete all exportdb data for given filepath"""
|
||||
conn = self._conn
|
||||
c = conn.cursor()
|
||||
filepath_normalized = self._normalize_filepath_relative(filepath)
|
||||
results = c.execute(
|
||||
"SELECT uuid FROM export_data WHERE filepath_normalized = ?;",
|
||||
(filepath_normalized,),
|
||||
).fetchall()
|
||||
count = 0
|
||||
for row in results:
|
||||
count += self.delete_data_for_uuid(row[0])
|
||||
return count
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def close(self):
|
||||
"""close the database connection"""
|
||||
try:
|
||||
self._conn.close()
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
self._conn.close()
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def _open_export_db(self, dbfile):
|
||||
"""open export database and return a db connection
|
||||
if dbfile does not exist, will create and initialize the database
|
||||
@@ -312,8 +379,6 @@ class ExportDB:
|
||||
|
||||
if not os.path.isfile(dbfile):
|
||||
conn = self._get_db_connection(dbfile)
|
||||
if not conn:
|
||||
raise Exception(f"Error getting connection to database {dbfile}")
|
||||
self._create_or_migrate_db_tables(conn)
|
||||
self.was_created = True
|
||||
self.was_upgraded = ()
|
||||
@@ -337,16 +402,12 @@ class ExportDB:
|
||||
|
||||
return conn
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def _get_db_connection(self, dbfile):
|
||||
"""return db connection to dbname"""
|
||||
try:
|
||||
conn = sqlite3.connect(dbfile)
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
conn = None
|
||||
|
||||
return conn
|
||||
return sqlite3.connect(dbfile)
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def _get_database_version(self, conn):
|
||||
"""return tuple of (osxphotos, exportdb) versions for database connection conn"""
|
||||
version_info = conn.execute(
|
||||
@@ -442,18 +503,15 @@ class ExportDB:
|
||||
""" CREATE UNIQUE INDEX IF NOT EXISTS idx_detected_text on detected_text (uuid);""",
|
||||
]
|
||||
# create the tables if needed
|
||||
try:
|
||||
c = conn.cursor()
|
||||
for cmd in sql_commands:
|
||||
c.execute(cmd)
|
||||
c.execute(
|
||||
"INSERT INTO version(osxphotos, exportdb) VALUES (?, ?);",
|
||||
(__version__, OSXPHOTOS_EXPORTDB_VERSION),
|
||||
)
|
||||
c.execute("INSERT INTO about(about) VALUES (?);", (OSXPHOTOS_ABOUT_STRING,))
|
||||
conn.commit()
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
c = conn.cursor()
|
||||
for cmd in sql_commands:
|
||||
c.execute(cmd)
|
||||
c.execute(
|
||||
"INSERT INTO version(osxphotos, exportdb) VALUES (?, ?);",
|
||||
(__version__, OSXPHOTOS_EXPORTDB_VERSION),
|
||||
)
|
||||
c.execute("INSERT INTO about(about) VALUES (?);", (OSXPHOTOS_ABOUT_STRING,))
|
||||
conn.commit()
|
||||
|
||||
# perform needed migrations
|
||||
if version[1] < "4.3":
|
||||
@@ -470,6 +528,10 @@ class ExportDB:
|
||||
# create report_data table
|
||||
self._migrate_6_0_to_7_0(conn)
|
||||
|
||||
if version[1] < "7.1":
|
||||
# add timestamp to export_data
|
||||
self._migrate_7_0_to_7_1(conn)
|
||||
|
||||
conn.execute("VACUUM;")
|
||||
conn.commit()
|
||||
|
||||
@@ -478,6 +540,7 @@ class ExportDB:
|
||||
with suppress(Exception):
|
||||
self._conn.close()
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def _insert_run_info(self):
|
||||
dt = datetime.datetime.now(datetime.timezone.utc).isoformat()
|
||||
python_path = sys.executable
|
||||
@@ -485,16 +548,13 @@ class ExportDB:
|
||||
args = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else ""
|
||||
cwd = os.getcwd()
|
||||
conn = self._conn
|
||||
try:
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"INSERT INTO runs (datetime, python_path, script_name, args, cwd) VALUES (?, ?, ?, ?, ?)",
|
||||
(dt, python_path, cmd, args, cwd),
|
||||
)
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"INSERT INTO runs (datetime, python_path, script_name, args, cwd) VALUES (?, ?, ?, ?, ?)",
|
||||
(dt, python_path, cmd, args, cwd),
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
conn.commit()
|
||||
|
||||
def _relative_filepath(self, filepath: Union[str, pathlib.Path]) -> str:
|
||||
"""return filepath relative to self._path"""
|
||||
@@ -550,158 +610,169 @@ class ExportDB:
|
||||
|
||||
def _migrate_4_3_to_5_0(self, conn):
|
||||
"""Migrate database from version 4.3 to 5.0"""
|
||||
try:
|
||||
c = conn.cursor()
|
||||
# add metadata column to files to support --force-update
|
||||
c.execute("ALTER TABLE files ADD COLUMN metadata TEXT;")
|
||||
conn.commit()
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
c = conn.cursor()
|
||||
# add metadata column to files to support --force-update
|
||||
c.execute("ALTER TABLE files ADD COLUMN metadata TEXT;")
|
||||
conn.commit()
|
||||
|
||||
def _migrate_5_0_to_6_0(self, conn):
|
||||
try:
|
||||
c = conn.cursor()
|
||||
c = conn.cursor()
|
||||
|
||||
# add export_data table
|
||||
c.execute(
|
||||
""" CREATE TABLE IF NOT EXISTS export_data(
|
||||
id INTEGER PRIMARY KEY,
|
||||
filepath_normalized TEXT NOT NULL,
|
||||
filepath TEXT NOT NULL,
|
||||
uuid TEXT NOT NULL,
|
||||
src_mode INTEGER,
|
||||
src_size INTEGER,
|
||||
src_mtime REAL,
|
||||
dest_mode INTEGER,
|
||||
dest_size INTEGER,
|
||||
dest_mtime REAL,
|
||||
digest TEXT,
|
||||
exifdata JSON,
|
||||
export_options INTEGER,
|
||||
UNIQUE(filepath_normalized)
|
||||
); """,
|
||||
)
|
||||
c.execute(
|
||||
""" CREATE UNIQUE INDEX IF NOT EXISTS idx_export_data_filepath_normalized on export_data (filepath_normalized); """,
|
||||
)
|
||||
# add export_data table
|
||||
c.execute(
|
||||
""" CREATE TABLE IF NOT EXISTS export_data(
|
||||
id INTEGER PRIMARY KEY,
|
||||
filepath_normalized TEXT NOT NULL,
|
||||
filepath TEXT NOT NULL,
|
||||
uuid TEXT NOT NULL,
|
||||
src_mode INTEGER,
|
||||
src_size INTEGER,
|
||||
src_mtime REAL,
|
||||
dest_mode INTEGER,
|
||||
dest_size INTEGER,
|
||||
dest_mtime REAL,
|
||||
digest TEXT,
|
||||
exifdata JSON,
|
||||
export_options INTEGER,
|
||||
UNIQUE(filepath_normalized)
|
||||
); """,
|
||||
)
|
||||
c.execute(
|
||||
""" CREATE UNIQUE INDEX IF NOT EXISTS idx_export_data_filepath_normalized on export_data (filepath_normalized); """,
|
||||
)
|
||||
|
||||
# migrate data
|
||||
c.execute(
|
||||
""" INSERT INTO export_data (filepath_normalized, filepath, uuid) SELECT filepath_normalized, filepath, uuid FROM files;""",
|
||||
)
|
||||
c.execute(
|
||||
""" UPDATE export_data
|
||||
SET (src_mode, src_size, src_mtime) =
|
||||
(SELECT mode, size, mtime
|
||||
FROM edited
|
||||
WHERE export_data.filepath_normalized = edited.filepath_normalized);
|
||||
""",
|
||||
)
|
||||
c.execute(
|
||||
""" UPDATE export_data
|
||||
SET (dest_mode, dest_size, dest_mtime) =
|
||||
(SELECT orig_mode, orig_size, orig_mtime
|
||||
FROM files
|
||||
WHERE export_data.filepath_normalized = files.filepath_normalized);
|
||||
""",
|
||||
)
|
||||
c.execute(
|
||||
""" UPDATE export_data SET digest =
|
||||
(SELECT metadata FROM files
|
||||
WHERE files.filepath_normalized = export_data.filepath_normalized
|
||||
); """
|
||||
)
|
||||
c.execute(
|
||||
""" UPDATE export_data SET exifdata =
|
||||
(SELECT json_exifdata FROM exifdata
|
||||
WHERE exifdata.filepath_normalized = export_data.filepath_normalized
|
||||
); """
|
||||
)
|
||||
# migrate data
|
||||
c.execute(
|
||||
""" INSERT INTO export_data (filepath_normalized, filepath, uuid) SELECT filepath_normalized, filepath, uuid FROM files;""",
|
||||
)
|
||||
c.execute(
|
||||
""" UPDATE export_data
|
||||
SET (src_mode, src_size, src_mtime) =
|
||||
(SELECT mode, size, mtime
|
||||
FROM edited
|
||||
WHERE export_data.filepath_normalized = edited.filepath_normalized);
|
||||
""",
|
||||
)
|
||||
c.execute(
|
||||
""" UPDATE export_data
|
||||
SET (dest_mode, dest_size, dest_mtime) =
|
||||
(SELECT orig_mode, orig_size, orig_mtime
|
||||
FROM files
|
||||
WHERE export_data.filepath_normalized = files.filepath_normalized);
|
||||
""",
|
||||
)
|
||||
c.execute(
|
||||
""" UPDATE export_data SET digest =
|
||||
(SELECT metadata FROM files
|
||||
WHERE files.filepath_normalized = export_data.filepath_normalized
|
||||
); """
|
||||
)
|
||||
c.execute(
|
||||
""" UPDATE export_data SET exifdata =
|
||||
(SELECT json_exifdata FROM exifdata
|
||||
WHERE exifdata.filepath_normalized = export_data.filepath_normalized
|
||||
); """
|
||||
)
|
||||
|
||||
# create config table
|
||||
c.execute(
|
||||
""" CREATE TABLE IF NOT EXISTS config (
|
||||
id INTEGER PRIMARY KEY,
|
||||
datetime TEXT,
|
||||
config TEXT
|
||||
); """
|
||||
)
|
||||
# create config table
|
||||
c.execute(
|
||||
""" CREATE TABLE IF NOT EXISTS config (
|
||||
id INTEGER PRIMARY KEY,
|
||||
datetime TEXT,
|
||||
config TEXT
|
||||
); """
|
||||
)
|
||||
|
||||
# create photoinfo table
|
||||
c.execute(
|
||||
""" CREATE TABLE IF NOT EXISTS photoinfo (
|
||||
id INTEGER PRIMARY KEY,
|
||||
uuid TEXT NOT NULL,
|
||||
photoinfo JSON,
|
||||
UNIQUE(uuid)
|
||||
); """
|
||||
)
|
||||
c.execute(
|
||||
"""CREATE UNIQUE INDEX IF NOT EXISTS idx_photoinfo_uuid on photoinfo (uuid);"""
|
||||
)
|
||||
c.execute(
|
||||
""" INSERT INTO photoinfo (uuid, photoinfo) SELECT uuid, json_info FROM info;"""
|
||||
)
|
||||
# create photoinfo table
|
||||
c.execute(
|
||||
""" CREATE TABLE IF NOT EXISTS photoinfo (
|
||||
id INTEGER PRIMARY KEY,
|
||||
uuid TEXT NOT NULL,
|
||||
photoinfo JSON,
|
||||
UNIQUE(uuid)
|
||||
); """
|
||||
)
|
||||
c.execute(
|
||||
"""CREATE UNIQUE INDEX IF NOT EXISTS idx_photoinfo_uuid on photoinfo (uuid);"""
|
||||
)
|
||||
c.execute(
|
||||
""" INSERT INTO photoinfo (uuid, photoinfo) SELECT uuid, json_info FROM info;"""
|
||||
)
|
||||
|
||||
# drop indexes no longer needed
|
||||
c.execute("DROP INDEX IF EXISTS idx_files_filepath_normalized;")
|
||||
c.execute("DROP INDEX IF EXISTS idx_exifdata_filename;")
|
||||
c.execute("DROP INDEX IF EXISTS idx_edited_filename;")
|
||||
c.execute("DROP INDEX IF EXISTS idx_converted_filename;")
|
||||
c.execute("DROP INDEX IF EXISTS idx_sidecar_filename;")
|
||||
c.execute("DROP INDEX IF EXISTS idx_detected_text;")
|
||||
# drop indexes no longer needed
|
||||
c.execute("DROP INDEX IF EXISTS idx_files_filepath_normalized;")
|
||||
c.execute("DROP INDEX IF EXISTS idx_exifdata_filename;")
|
||||
c.execute("DROP INDEX IF EXISTS idx_edited_filename;")
|
||||
c.execute("DROP INDEX IF EXISTS idx_converted_filename;")
|
||||
c.execute("DROP INDEX IF EXISTS idx_sidecar_filename;")
|
||||
c.execute("DROP INDEX IF EXISTS idx_detected_text;")
|
||||
|
||||
# drop tables no longer needed
|
||||
c.execute("DROP TABLE IF EXISTS files;")
|
||||
c.execute("DROP TABLE IF EXISTS info;")
|
||||
c.execute("DROP TABLE IF EXISTS exifdata;")
|
||||
c.execute("DROP TABLE IF EXISTS edited;")
|
||||
c.execute("DROP TABLE IF EXISTS converted;")
|
||||
c.execute("DROP TABLE IF EXISTS sidecar;")
|
||||
c.execute("DROP TABLE IF EXISTS detected_text;")
|
||||
# drop tables no longer needed
|
||||
c.execute("DROP TABLE IF EXISTS files;")
|
||||
c.execute("DROP TABLE IF EXISTS info;")
|
||||
c.execute("DROP TABLE IF EXISTS exifdata;")
|
||||
c.execute("DROP TABLE IF EXISTS edited;")
|
||||
c.execute("DROP TABLE IF EXISTS converted;")
|
||||
c.execute("DROP TABLE IF EXISTS sidecar;")
|
||||
c.execute("DROP TABLE IF EXISTS detected_text;")
|
||||
|
||||
conn.commit()
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
conn.commit()
|
||||
|
||||
def _migrate_6_0_to_7_0(self, conn):
|
||||
try:
|
||||
c = conn.cursor()
|
||||
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(
|
||||
"""CREATE TABLE IF NOT EXISTS export_results_data (
|
||||
id INTEGER PRIMARY KEY,
|
||||
datetime TEXT,
|
||||
export_results BLOB
|
||||
);"""
|
||||
"""INSERT INTO export_results_data (datetime, export_results) VALUES (?, ?);""",
|
||||
(datetime.datetime.now().isoformat(), b""),
|
||||
)
|
||||
# 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)
|
||||
# sleep a tiny bit just to ensure time stamps increment
|
||||
time.sleep(0.001)
|
||||
conn.commit()
|
||||
|
||||
def _migrate_7_0_to_7_1(self, conn):
|
||||
c = conn.cursor()
|
||||
c.execute("""ALTER TABLE export_data ADD COLUMN timestamp DATETIME;""")
|
||||
c.execute(
|
||||
"""
|
||||
CREATE TRIGGER insert_timestamp_trigger
|
||||
AFTER INSERT ON export_data
|
||||
BEGIN
|
||||
UPDATE export_data SET timestamp = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE id = NEW.id;
|
||||
END;
|
||||
"""
|
||||
)
|
||||
c.execute(
|
||||
"""
|
||||
CREATE TRIGGER update_timestamp_trigger
|
||||
AFTER UPDATE On export_data
|
||||
BEGIN
|
||||
UPDATE export_data SET timestamp = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE id = NEW.id;
|
||||
END;
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def _perform_db_maintenace(self, conn):
|
||||
"""Perform database maintenance"""
|
||||
try:
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"""DELETE FROM config
|
||||
WHERE id < (
|
||||
SELECT MIN(id)
|
||||
FROM (SELECT id FROM config ORDER BY id DESC LIMIT 9)
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"""DELETE FROM config
|
||||
WHERE id < (
|
||||
SELECT MIN(id)
|
||||
FROM (SELECT id FROM config ORDER BY id DESC LIMIT 9)
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
class ExportDBInMemory(ExportDB):
|
||||
@@ -753,14 +824,13 @@ class ExportDBInMemory(ExportDB):
|
||||
conn_on_disk.commit()
|
||||
conn_on_disk.close()
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def close(self):
|
||||
"""close the database connection"""
|
||||
try:
|
||||
if self._conn:
|
||||
self._conn.close()
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
if self._conn:
|
||||
self._conn.close()
|
||||
|
||||
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||
def _open_export_db(self, dbfile): # sourcery skip: raise-specific-error
|
||||
"""open export database and return a db connection
|
||||
returns: connection to the database
|
||||
@@ -794,13 +864,7 @@ class ExportDBInMemory(ExportDB):
|
||||
|
||||
def _get_db_connection(self):
|
||||
"""return db connection to in memory database"""
|
||||
try:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
except Error as e:
|
||||
logging.warning(e)
|
||||
conn = None
|
||||
|
||||
return conn
|
||||
return sqlite3.connect(":memory:")
|
||||
|
||||
def _dump_db(self, conn: sqlite3.Connection) -> StringIO:
|
||||
"""dump sqlite db to a string buffer"""
|
||||
@@ -1048,6 +1112,21 @@ class ExportRecord:
|
||||
if not self._context_manager:
|
||||
conn.commit()
|
||||
|
||||
@property
|
||||
def timestamp(self):
|
||||
"""returns the timestamp value"""
|
||||
conn = self._conn
|
||||
c = conn.cursor()
|
||||
if row := c.execute(
|
||||
"SELECT timestamp FROM export_data WHERE filepath_normalized = ?;",
|
||||
(self._filepath_normalized,),
|
||||
).fetchone():
|
||||
return row[0]
|
||||
|
||||
raise ValueError(
|
||||
f"No timestamp found in database for {self._filepath_normalized}"
|
||||
)
|
||||
|
||||
def asdict(self):
|
||||
"""Return dict of self"""
|
||||
exifdata = json.loads(self.exifdata) if self.exifdata else None
|
||||
@@ -1056,6 +1135,7 @@ class ExportRecord:
|
||||
"filepath": self.filepath,
|
||||
"filepath_normalized": self.filepath_normalized,
|
||||
"uuid": self.uuid,
|
||||
"timestamp": self.timestamp,
|
||||
"digest": self.digest,
|
||||
"src_sig": self.src_sig,
|
||||
"dest_sig": self.dest_sig,
|
||||
|
||||
@@ -92,11 +92,13 @@ class PersonInfo:
|
||||
return f"PersonInfo(name={self.name}, display_name={self.display_name}, uuid={self.uuid}, facecount={self.facecount})"
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, type(self)):
|
||||
return False
|
||||
|
||||
return all(
|
||||
getattr(self, field) == getattr(other, field) for field in ["_db", "_pk"]
|
||||
return (
|
||||
all(
|
||||
getattr(self, field) == getattr(other, field)
|
||||
for field in ["_db", "_pk"]
|
||||
)
|
||||
if isinstance(other, type(self))
|
||||
else False
|
||||
)
|
||||
|
||||
def __ne__(self, other):
|
||||
@@ -127,23 +129,14 @@ class FaceInfo:
|
||||
self._person_pk = face["person"]
|
||||
self.center_x = face["centerx"]
|
||||
self.center_y = face["centery"]
|
||||
self.mouth_x = face["mouthx"]
|
||||
self.mouth_y = face["mouthy"]
|
||||
self.left_eye_x = face["lefteyex"]
|
||||
self.left_eye_y = face["lefteyey"]
|
||||
self.right_eye_x = face["righteyex"]
|
||||
self.right_eye_y = face["righteyey"]
|
||||
self.size = face["size"]
|
||||
self.quality = face["quality"]
|
||||
self.source_width = face["sourcewidth"]
|
||||
self.source_height = face["sourceheight"]
|
||||
self.has_smile = face["has_smile"]
|
||||
self.left_eye_closed = face["left_eye_closed"]
|
||||
self.right_eye_closed = face["right_eye_closed"]
|
||||
self.manual = face["manual"]
|
||||
self.face_type = face["facetype"]
|
||||
self.age_type = face["agetype"]
|
||||
# self.bald_type = face["baldtype"]
|
||||
self.eye_makeup_type = face["eyemakeuptype"]
|
||||
self.eye_state = face["eyestate"]
|
||||
self.facial_hair_type = face["facialhairtype"]
|
||||
@@ -174,33 +167,6 @@ class FaceInfo:
|
||||
size_reference = photo.width if photo.width > photo.height else photo.height
|
||||
return self.size * size_reference
|
||||
|
||||
@property
|
||||
def mouth(self):
|
||||
"""Coordinates, in PIL format, for mouth position
|
||||
|
||||
Returns:
|
||||
tuple of coordinates in form (x, y)
|
||||
"""
|
||||
return self._make_point_with_rotation((self.mouth_x, self.mouth_y))
|
||||
|
||||
@property
|
||||
def left_eye(self):
|
||||
"""Coordinates, in PIL format, for left eye position
|
||||
|
||||
Returns:
|
||||
tuple of coordinates in form (x, y)
|
||||
"""
|
||||
return self._make_point_with_rotation((self.left_eye_x, self.left_eye_y))
|
||||
|
||||
@property
|
||||
def right_eye(self):
|
||||
"""Coordinates, in PIL format, for right eye position
|
||||
|
||||
Returns:
|
||||
tuple of coordinates in form (x, y)
|
||||
"""
|
||||
return self._make_point_with_rotation((self.right_eye_x, self.right_eye_y))
|
||||
|
||||
@property
|
||||
def person_info(self):
|
||||
"""PersonInfo instance for person associated with this face"""
|
||||
@@ -415,15 +381,6 @@ class FaceInfo:
|
||||
"center_x": self.center_x,
|
||||
"center_y": self.center_y,
|
||||
"center": self.center,
|
||||
"mouth_x": self.mouth_x,
|
||||
"mouth_y": self.mouth_y,
|
||||
"mouth": self.mouth,
|
||||
"left_eye_x": self.left_eye_x,
|
||||
"left_eye_y": self.left_eye_y,
|
||||
"left_eye": self.left_eye,
|
||||
"right_eye_x": self.right_eye_x,
|
||||
"right_eye_y": self.right_eye_y,
|
||||
"right_eye": self.right_eye,
|
||||
"size": self.size,
|
||||
"face_rect": self.face_rect(),
|
||||
"mpri_reg_rect": self.mpri_reg_rect._asdict(),
|
||||
@@ -435,12 +392,9 @@ class FaceInfo:
|
||||
"source_width": self.source_width,
|
||||
"source_height": self.source_height,
|
||||
"has_smile": self.has_smile,
|
||||
"left_eye_closed": self.left_eye_closed,
|
||||
"right_eye_closed": self.right_eye_closed,
|
||||
"manual": self.manual,
|
||||
"face_type": self.face_type,
|
||||
"age_type": self.age_type,
|
||||
# "bald_type": self.bald_type,
|
||||
"eye_makeup_type": self.eye_makeup_type,
|
||||
"eye_state": self.eye_state,
|
||||
"facial_hair_type": self.facial_hair_type,
|
||||
|
||||
@@ -44,7 +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 hexdigest, increment_filename, lineno, list_directory
|
||||
from .utils import (
|
||||
hexdigest,
|
||||
increment_filename,
|
||||
increment_filename_with_count,
|
||||
lineno,
|
||||
list_directory,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ExportError",
|
||||
@@ -488,7 +494,7 @@ class PhotoExporter:
|
||||
src = staged_files.edited if options.edited else staged_files.original
|
||||
|
||||
# get the right destination path depending on options.update, etc.
|
||||
dest = self._get_dest_path(src, dest, options)
|
||||
dest = self._get_dest_path(dest, options)
|
||||
|
||||
self._render_options.filepath = str(dest)
|
||||
all_results = ExportResults()
|
||||
@@ -642,12 +648,11 @@ class PhotoExporter:
|
||||
return edited_filename
|
||||
|
||||
def _get_dest_path(
|
||||
self, src: str, dest: pathlib.Path, options: ExportOptions
|
||||
self, dest: pathlib.Path, options: ExportOptions
|
||||
) -> pathlib.Path:
|
||||
"""If destination exists find match in ExportDB, on disk, or add (1), (2), and so on to filename to get a valid destination
|
||||
|
||||
Args:
|
||||
src (str): source file path
|
||||
dest (str): destination path
|
||||
options (ExportOptions): Export options
|
||||
|
||||
@@ -663,6 +668,10 @@ class PhotoExporter:
|
||||
f"destination exists ({dest}); overwrite={options.overwrite}, increment={options.increment}"
|
||||
)
|
||||
|
||||
# if overwrite, we don't care if the file exists or not
|
||||
if options.overwrite:
|
||||
return dest
|
||||
|
||||
# if not update or overwrite, check to see if file exists and if so, add (1), (2), etc
|
||||
# until we find one that works
|
||||
# Photos checks the stem and adds (1), (2), etc which avoids collision with sidecars
|
||||
@@ -675,29 +684,36 @@ class PhotoExporter:
|
||||
return pathlib.Path(increment_filename(dest))
|
||||
|
||||
# if update and file exists, need to check to see if it's the right file by checking export db
|
||||
if (options.update or options.force_update) and dest.exists() and src:
|
||||
if options.update or options.force_update:
|
||||
export_db = options.export_db
|
||||
# destination exists, check to see if destination is the right UUID
|
||||
dest_uuid = export_db.get_uuid_for_file(dest)
|
||||
if dest_uuid != self.photo.uuid:
|
||||
# not the right file, find the right one
|
||||
# find files that match "dest_name (*.ext" (e.g. "dest_name (1).jpg", "dest_name (2).jpg)", ...)
|
||||
dest_files = list_directory(
|
||||
dest.parent,
|
||||
startswith=f"{dest.stem} (",
|
||||
endswith=dest.suffix,
|
||||
include_path=True,
|
||||
)
|
||||
for file_ in dest_files:
|
||||
dest_uuid = export_db.get_uuid_for_file(file_)
|
||||
if dest_uuid == self.photo.uuid:
|
||||
dest = pathlib.Path(file_)
|
||||
break
|
||||
else:
|
||||
# increment the destination file
|
||||
dest = pathlib.Path(increment_filename(dest))
|
||||
if dest_uuid is None and not dest.exists():
|
||||
# destination doesn't exist in export db and doesn't exist on disk
|
||||
# so we can just use it
|
||||
return dest
|
||||
|
||||
# either dest was updated in the if clause above or not updated at all
|
||||
if dest_uuid == self.photo.uuid:
|
||||
# destination is the right file
|
||||
return dest
|
||||
|
||||
# either dest_uuid is wrong or file exists and there's no associated UUID, so find a name that matches
|
||||
# or create a new name if no match
|
||||
# find files that match "dest_name (*.ext" (e.g. "dest_name (1).jpg", "dest_name (2).jpg)", ...)
|
||||
# first, find all matching files in export db and see if there's a match
|
||||
if dest_target := export_db.get_target_for_file(self.photo.uuid, dest):
|
||||
# there's a match so use that
|
||||
return pathlib.Path(dest_target)
|
||||
|
||||
# no match so need to create a new name
|
||||
# increment the destination file until we find one that doesn't exist and doesn't match another uuid in the database
|
||||
count = 0
|
||||
dest, count = increment_filename_with_count(dest, count)
|
||||
count += 1
|
||||
while export_db.get_uuid_for_file(dest) is not None:
|
||||
dest, count = increment_filename_with_count(dest, count)
|
||||
return pathlib.Path(dest)
|
||||
|
||||
# fail safe...I can't think of a case that gets here
|
||||
return dest
|
||||
|
||||
def _should_update_photo(
|
||||
@@ -1036,9 +1052,9 @@ class PhotoExporter:
|
||||
|
||||
def _export_photo(
|
||||
self,
|
||||
src,
|
||||
dest,
|
||||
options,
|
||||
src: str,
|
||||
dest: pathlib.Path,
|
||||
options: ExportOptions,
|
||||
):
|
||||
"""Helper function for export()
|
||||
Does the actual copy or hardlink taking the appropriate
|
||||
@@ -1060,6 +1076,7 @@ class PhotoExporter:
|
||||
ValueError if export_as_hardlink and convert_to_jpeg both True
|
||||
"""
|
||||
|
||||
verbose = options.verbose or self._verbose
|
||||
if options.export_as_hardlink and options.convert_to_jpeg:
|
||||
raise ValueError(
|
||||
"export_as_hardlink and convert_to_jpeg cannot both be True"
|
||||
@@ -1149,6 +1166,9 @@ class PhotoExporter:
|
||||
|
||||
try:
|
||||
fileutil.copy(src, dest_str)
|
||||
verbose(
|
||||
f"Exported {self._filename(self.photo.original_filename)} to {self._filepath(dest_str)}"
|
||||
)
|
||||
except Exception as e:
|
||||
raise ExportError(
|
||||
f"Error copying file {src} to {dest_str}: {e} ({lineno(__file__)})"
|
||||
@@ -1444,7 +1464,7 @@ class PhotoExporter:
|
||||
) -> ExportResults:
|
||||
"""Write exif metadata to src file using exiftool
|
||||
|
||||
Caution: This method modifies *src*, not *dest*,
|
||||
Caution: This method modifies *src*, not *dest*,
|
||||
so src must be a copy of the original file if you don't want the source modified;
|
||||
it also does not write to dest (dest is the intended destination for purposes of
|
||||
referencing the export database. This allows the exiftool update to be done on the
|
||||
|
||||
@@ -3,6 +3,8 @@ PhotoInfo class
|
||||
Represents a single photo in the Photos library and provides access to the photo's attributes
|
||||
PhotosDB.photos() returns a list of PhotoInfo objects
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import dataclasses
|
||||
import datetime
|
||||
import json
|
||||
@@ -10,14 +12,16 @@ import logging
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import plistlib
|
||||
from datetime import timedelta, timezone
|
||||
from functools import cached_property
|
||||
from typing import Optional
|
||||
from typing import Dict, Optional
|
||||
|
||||
import yaml
|
||||
from osxmetadata import OSXMetaData
|
||||
|
||||
from ._constants import (
|
||||
_DB_TABLE_NAMES,
|
||||
_MOVIE_TYPE,
|
||||
_PHOTO_TYPE,
|
||||
_PHOTOS_4_ALBUM_KIND,
|
||||
@@ -1372,6 +1376,30 @@ class PhotoInfo:
|
||||
useful for detecting changes in any property/metadata of the photo"""
|
||||
return hexdigest(self.json())
|
||||
|
||||
@cached_property
|
||||
def cloud_metadata(self) -> Dict:
|
||||
"""Returns contents of ZCLOUDMASTERMEDIAMETADATA as dict"""
|
||||
# This is a large blob of data so don't load it unless requested
|
||||
asset_table = _DB_TABLE_NAMES[self._db._photos_ver]["ASSET"]
|
||||
sql_cloud_metadata = f"""
|
||||
SELECT ZCLOUDMASTERMEDIAMETADATA.ZDATA
|
||||
FROM ZCLOUDMASTERMEDIAMETADATA
|
||||
JOIN ZCLOUDMASTER ON ZCLOUDMASTER.Z_PK = ZCLOUDMASTERMEDIAMETADATA.ZCLOUDMASTER
|
||||
JOIN {asset_table} on {asset_table}.ZMASTER = ZCLOUDMASTER.Z_PK
|
||||
WHERE {asset_table}.ZUUID = ?
|
||||
"""
|
||||
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
logging.debug(f"cloud_metadata not implemented for this database version")
|
||||
return {}
|
||||
|
||||
_, cursor = self._db.get_db_connection()
|
||||
metadata = {}
|
||||
if results := cursor.execute(sql_cloud_metadata, (self.uuid,)).fetchone():
|
||||
with contextlib.suppress(Exception):
|
||||
metadata = plistlib.loads(results[0])
|
||||
return metadata
|
||||
|
||||
def detected_text(self, confidence_threshold=TEXT_DETECTION_CONFIDENCE_THRESHOLD):
|
||||
"""Detects text in photo and returns lists of results as (detected text, confidence)
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
""" Methods for PhotosDB to add Photos face info
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION
|
||||
from ..utils import _open_sql_file, normalize_unicode
|
||||
from .photosdb_utils import get_db_version
|
||||
|
||||
|
||||
"""
|
||||
This module should be imported in the class defintion of PhotosDB in photosdb.py
|
||||
Do not import this module directly
|
||||
@@ -202,8 +200,8 @@ def _process_faceinfo_5(photosdb):
|
||||
ZDETECTEDFACE.ZHASSMILE,
|
||||
ZDETECTEDFACE.ZHIDDEN,
|
||||
ZDETECTEDFACE.ZISINTRASH,
|
||||
ZDETECTEDFACE.ZISLEFTEYECLOSED,
|
||||
ZDETECTEDFACE.ZISRIGHTEYECLOSED,
|
||||
NULL, -- ZDETECTEDFACE.ZISLEFTEYECLOSED
|
||||
NULL, -- ZDETECTEDFACE.ZISRIGHTEYECLOSED
|
||||
ZDETECTEDFACE.ZLIPMAKEUPTYPE,
|
||||
ZDETECTEDFACE.ZMANUAL,
|
||||
ZDETECTEDFACE.ZQUALITYMEASURE,
|
||||
@@ -213,17 +211,17 @@ def _process_faceinfo_5(photosdb):
|
||||
ZDETECTEDFACE.ZBLURSCORE,
|
||||
ZDETECTEDFACE.ZCENTERX,
|
||||
ZDETECTEDFACE.ZCENTERY,
|
||||
ZDETECTEDFACE.ZLEFTEYEX,
|
||||
ZDETECTEDFACE.ZLEFTEYEY,
|
||||
ZDETECTEDFACE.ZMOUTHX,
|
||||
ZDETECTEDFACE.ZMOUTHY,
|
||||
NULL, -- ZDETECTEDFACE.ZLEFTEYEX,
|
||||
NULL, -- ZDETECTEDFACE.ZLEFTEYEY,
|
||||
NULL, -- ZDETECTEDFACE.ZMOUTHX,
|
||||
NULL, -- ZDETECTEDFACE.ZMOUTHY,
|
||||
ZDETECTEDFACE.ZPOSEYAW,
|
||||
ZDETECTEDFACE.ZQUALITY,
|
||||
ZDETECTEDFACE.ZRIGHTEYEX,
|
||||
ZDETECTEDFACE.ZRIGHTEYEY,
|
||||
NULL, -- ZDETECTEDFACE.ZRIGHTEYEX,
|
||||
NULL, -- ZDETECTEDFACE.ZRIGHTEYEY,
|
||||
ZDETECTEDFACE.ZROLL,
|
||||
ZDETECTEDFACE.ZSIZE,
|
||||
ZDETECTEDFACE.ZYAW,
|
||||
NULL, -- ZDETECTEDFACE.ZYAW,
|
||||
ZDETECTEDFACE.ZMASTERIDENTIFIER
|
||||
FROM ZDETECTEDFACE
|
||||
JOIN {asset_table} ON {asset_table}.Z_PK = ZDETECTEDFACE.ZASSET
|
||||
@@ -237,7 +235,7 @@ def _process_faceinfo_5(photosdb):
|
||||
# 3 ZDETECTEDFACE.ZPERSON,
|
||||
# 4 ZPERSON.ZFULLNAME,
|
||||
# 5 ZDETECTEDFACE.ZAGETYPE,
|
||||
# 6 ZDETECTEDFACE.ZBALDTYPE, (Not available on Monterey)
|
||||
# 6 NULL -- ZDETECTEDFACE.ZBALDTYPE, (Not available on Monterey)
|
||||
# 7 ZDETECTEDFACE.ZEYEMAKEUPTYPE,
|
||||
# 8 ZDETECTEDFACE.ZEYESSTATE,
|
||||
# 9 ZDETECTEDFACE.ZFACIALHAIRTYPE,
|
||||
@@ -247,8 +245,8 @@ def _process_faceinfo_5(photosdb):
|
||||
# 13 ZDETECTEDFACE.ZHASSMILE,
|
||||
# 14 ZDETECTEDFACE.ZHIDDEN,
|
||||
# 15 ZDETECTEDFACE.ZISINTRASH,
|
||||
# 16 ZDETECTEDFACE.ZISLEFTEYECLOSED,
|
||||
# 17 ZDETECTEDFACE.ZISRIGHTEYECLOSED,
|
||||
# 16 NULL -- ZDETECTEDFACE.ZISLEFTEYECLOSED,
|
||||
# 17 NULL -- ZDETECTEDFACE.ZISRIGHTEYECLOSED,
|
||||
# 18 ZDETECTEDFACE.ZLIPMAKEUPTYPE,
|
||||
# 19 ZDETECTEDFACE.ZMANUAL,
|
||||
# 20 ZDETECTEDFACE.ZQUALITYMEASURE,
|
||||
@@ -258,17 +256,17 @@ def _process_faceinfo_5(photosdb):
|
||||
# 24 ZDETECTEDFACE.ZBLURSCORE,
|
||||
# 25 ZDETECTEDFACE.ZCENTERX,
|
||||
# 26 ZDETECTEDFACE.ZCENTERY,
|
||||
# 27 ZDETECTEDFACE.ZLEFTEYEX,
|
||||
# 28 ZDETECTEDFACE.ZLEFTEYEY,
|
||||
# 29 ZDETECTEDFACE.ZMOUTHX,
|
||||
# 30 ZDETECTEDFACE.ZMOUTHY,
|
||||
# 27 NULL -- ZDETECTEDFACE.ZLEFTEYEX, (Not available on Ventura)
|
||||
# 28 NULL -- ZDETECTEDFACE.ZLEFTEYEY, (Not available on Ventura)
|
||||
# 29 NULL -- ZDETECTEDFACE.ZMOUTHX, (Not available on Ventura)
|
||||
# 30 NULL -- ZDETECTEDFACE.ZMOUTHY, (Not available on Ventura)
|
||||
# 31 ZDETECTEDFACE.ZPOSEYAW,
|
||||
# 32 ZDETECTEDFACE.ZQUALITY,
|
||||
# 33 ZDETECTEDFACE.ZRIGHTEYEX,
|
||||
# 34 ZDETECTEDFACE.ZRIGHTEYEY,
|
||||
# 33 NULL -- ZDETECTEDFACE.ZRIGHTEYEX, (Not available on Ventura)
|
||||
# 34 NULL -- ZDETECTEDFACE.ZRIGHTEYEY, (Not available on Ventura)
|
||||
# 35 ZDETECTEDFACE.ZROLL,
|
||||
# 36 ZDETECTEDFACE.ZSIZE,
|
||||
# 37 ZDETECTEDFACE.ZYAW,
|
||||
# 37 NULL -- ZDETECTEDFACE.ZYAW, (Not available on Ventura)
|
||||
# 38 ZDETECTEDFACE.ZMASTERIDENTIFIER
|
||||
|
||||
for row in result:
|
||||
@@ -310,8 +308,8 @@ def _process_faceinfo_5(photosdb):
|
||||
face["righteyey"] = row[34]
|
||||
face["roll"] = row[35]
|
||||
face["size"] = row[36]
|
||||
face["yaw"] = row[37]
|
||||
face["pitch"] = 0.0 # not defined in Photos 5
|
||||
face["yaw"] = 0 # Photos 4 only (this is in Photos 5-7, but dropped in Ventura so just don't support it)
|
||||
face["pitch"] = 0 # not defined in Photos 5
|
||||
|
||||
photosdb._db_faceinfo_pk[pk] = face
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import tempfile
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Iterable
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
from unicodedata import normalize
|
||||
|
||||
import bitmath
|
||||
@@ -2858,16 +2858,16 @@ class PhotosDB:
|
||||
|
||||
def photos(
|
||||
self,
|
||||
keywords=None,
|
||||
uuid=None,
|
||||
persons=None,
|
||||
albums=None,
|
||||
images=True,
|
||||
movies=True,
|
||||
from_date=None,
|
||||
to_date=None,
|
||||
intrash=False,
|
||||
):
|
||||
keywords: Optional[List[str]] = None,
|
||||
uuid: Optional[List[str]] = None,
|
||||
persons: Optional[List[str]] = None,
|
||||
albums: Optional[List[str]] = None,
|
||||
images: bool = True,
|
||||
movies: bool = True,
|
||||
from_date: Optional[datetime] = None,
|
||||
to_date: Optional[datetime] = None,
|
||||
intrash: bool = False,
|
||||
) -> List[PhotoInfo]:
|
||||
"""Return a list of PhotoInfo objects
|
||||
If called with no args, returns the entire database of photos
|
||||
If called with args, returns photos matching the args (e.g. keywords, persons, etc.)
|
||||
|
||||
@@ -12,6 +12,7 @@ from .._constants import (
|
||||
_PHOTOS_5_VERSION,
|
||||
_PHOTOS_6_MODEL_VERSION,
|
||||
_PHOTOS_7_MODEL_VERSION,
|
||||
_PHOTOS_8_MODEL_VERSION,
|
||||
_TESTED_DB_VERSIONS,
|
||||
)
|
||||
from ..utils import _open_sql_file
|
||||
@@ -92,10 +93,12 @@ def get_db_model_version(db_file):
|
||||
return 6
|
||||
elif _PHOTOS_7_MODEL_VERSION[0] <= model_ver <= _PHOTOS_7_MODEL_VERSION[1]:
|
||||
return 7
|
||||
elif _PHOTOS_8_MODEL_VERSION[0] <= model_ver <= _PHOTOS_8_MODEL_VERSION[1]:
|
||||
return 8
|
||||
else:
|
||||
logging.warning(f"Unknown model version: {model_ver}")
|
||||
# cross our fingers and try latest version
|
||||
return 7
|
||||
return 8
|
||||
|
||||
|
||||
class UnknownLibraryVersion(Exception):
|
||||
|
||||
@@ -10,7 +10,7 @@ In its simplest form, a template statement has the form: `"{template_field}"`, f
|
||||
|
||||
Template statements may contain one or more modifiers. The full syntax is:
|
||||
|
||||
`"pretext{delim+template_field:subfield|filter(path_sep)[find,replace] conditional?bool_value,default}posttext"`
|
||||
`"pretext{delim+template_field:subfield(field_arg)|filter[find,replace] conditional?bool_value,default}posttext"`
|
||||
|
||||
Template statements are white-space sensitive meaning that white space (spaces, tabs) changes the meaning of the template statement.
|
||||
|
||||
@@ -31,6 +31,8 @@ e.g. if Photo keywords are `["foo","bar"]`:
|
||||
|
||||
`:subfield`: Some templates have sub-fields, For example, `{exiftool:IPTC:Make}`; the template_field is `exiftool` and the sub-field is `IPTC:Make`.
|
||||
|
||||
`(field_arg)`: optional arguments to pass to the field; for example, with `{folder_album}` this is used to pass the path separator used for joining folders and albums when rendering the field (default is "/" for `{folder_album}`).
|
||||
|
||||
`|filter`: You may optionally append one or more filter commands to the end of the template field using the vertical pipe ('|') symbol. Filters may be combined, separated by '|' as in: `{keyword|capitalize|parens}`.
|
||||
|
||||
Valid filters are:
|
||||
@@ -40,16 +42,6 @@ from osxphotos.phototemplate import FILTER_VALUES
|
||||
filter_help = "\n".join(f"- `{f}`: {descr}" for f, descr in FILTER_VALUES.items())
|
||||
cog.out(filter_help)
|
||||
]]]-->
|
||||
- `lower`: Convert value to lower case, e.g. 'Value' => 'value'.
|
||||
- `upper`: Convert value to upper case, e.g. 'Value' => 'VALUE'.
|
||||
- `strip`: Strip whitespace from beginning/end of value, e.g. ' Value ' => 'Value'.
|
||||
- `titlecase`: Convert value to title case, e.g. 'my value' => 'My Value'.
|
||||
- `capitalize`: Capitalize first word of value and convert other words to lower case, e.g. 'MY VALUE' => 'My value'.
|
||||
- `braces`: Enclose value in curly braces, e.g. 'value => '{value}'.
|
||||
- `parens`: Enclose value in parentheses, e.g. 'value' => '(value')
|
||||
- `brackets`: Enclose value in brackets, e.g. 'value' => '[value]'
|
||||
- `shell_quote`: Quotes the value for safe usage in the shell, e.g. My file.jpeg => 'My file.jpeg'; only adds quotes if needed.
|
||||
- `function`: Run custom python function to filter value; use in format 'function:/path/to/file.py::function_name'. See example at <https://github.com/RhetTbull/osxphotos/blob/master/examples/template_filter.py>
|
||||
<!--[[[end]]]-->
|
||||
|
||||
e.g. if Photo keywords are `["FOO","bar"]`:
|
||||
@@ -63,8 +55,6 @@ e.g. if Photo description is "my description":
|
||||
|
||||
- `"{descr|titlecase}"` renders to: `"My Description"`
|
||||
|
||||
`(path_sep)`: optional path separator to use when joining path-like fields, for example `{folder_album}`. Default is "/".
|
||||
|
||||
e.g. If Photo is in `Album1` in `Folder1`:
|
||||
|
||||
- `"{folder_album}"` renders to `["Folder1/Album1"]`
|
||||
@@ -107,7 +97,7 @@ This can be used to rename files as well, for example:
|
||||
|
||||
This renames any photo that is a favorite as 'Favorite-ImageName.jpg' (where 'ImageName.jpg' is the original name of the photo) and all other photos with the unmodified original name.
|
||||
|
||||
`?bool_value`: Template fields may be evaluated as boolean (True/False) by appending "?" after the field name (and following "(path_sep)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is `"{hdr}"`) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is `"{title}"`) then the default value following a "," will be used.
|
||||
`?bool_value`: Template fields may be evaluated as boolean (True/False) by appending "?" after the field name (and following "(field_arg)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is `"{hdr}"`) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is `"{title}"`) then the default value following a "," will be used.
|
||||
|
||||
e.g. if photo is an HDR image,
|
||||
|
||||
@@ -137,3 +127,13 @@ Either or both bool_value or default (False value) may be empty which would resu
|
||||
If you want to include "{" or "}" in the output, use "{openbrace}" or "{closebrace}" template substitution.
|
||||
|
||||
e.g. `"{created.year}/{openbrace}{title}{closebrace}"` would result in `"2020/{Photo Title}"`.
|
||||
|
||||
**Variables**
|
||||
|
||||
You can define variables for later use in the template string using the format `{var:NAME,VALUE}`. Variables may then be referenced using the format `%NAME`. For example: `{var:foo,bar}` defines the variable `%foo` to have value `bar`. This can be useful if you want to re-use a complex template value in multiple places within your template string or for allowing the use of characters that would otherwise be prohibited in a template string. For example, the "pipe" (`|`) character is not allowed in a find/replace pair but you can get around this limitation like so: `{var:pipe,{pipe}}{title[-,%pipe]}` which replaces the `-` character with `|` (the value of `%pipe`).
|
||||
|
||||
Variables can also be referenced as fields in the template string, for example: `{var:year,created.year}{original_name}-{%year}`. In some cases, use of variables can make your template string more readable. Variables can be used as template fields, as values for filters, as values for conditional operations, or as default values. When used as a conditional value or default value, variables should be treated like any other field and enclosed in braces as conditional and default values are evaluated as template strings. For example: `{var:name,Katie}{person contains {%name}?{%name},Not-{%name}}`.
|
||||
|
||||
If you need to use a `%` (percent sign character), you can escape the percent sign by using `%%`. You can also use the `{percent}` template field where a template field is required. For example:
|
||||
|
||||
`{title[:,%%]}` replaces the `:` with `%` and `{title contains Foo?{title}{percent},{title}}` adds `%` to the title if it contains `Foo`.
|
||||
@@ -6,7 +6,7 @@ In its simplest form, a template statement has the form: `"{template_field}"`, f
|
||||
|
||||
Template statements may contain one or more modifiers. The full syntax is:
|
||||
|
||||
`"pretext{delim+template_field:subfield|filter(path_sep)[find,replace] conditional?bool_value,default}posttext"`
|
||||
`"pretext{delim+template_field:subfield(field_arg)|filter[find,replace] conditional?bool_value,default}posttext"`
|
||||
|
||||
Template statements are white-space sensitive meaning that white space (spaces, tabs) changes the meaning of the template statement.
|
||||
|
||||
@@ -27,6 +27,8 @@ e.g. if Photo keywords are `["foo","bar"]`:
|
||||
|
||||
`:subfield`: Some templates have sub-fields, For example, `{exiftool:IPTC:Make}`; the template_field is `exiftool` and the sub-field is `IPTC:Make`.
|
||||
|
||||
`(field_arg)`: optional arguments to pass to the field; for example, with `{folder_album}` this is used to pass the path separator used for joining folders and albums when rendering the field (default is "/" for `{folder_album}`).
|
||||
|
||||
`|filter`: You may optionally append one or more filter commands to the end of the template field using the vertical pipe ('|') symbol. Filters may be combined, separated by '|' as in: `{keyword|capitalize|parens}`.
|
||||
|
||||
Valid filters are:
|
||||
@@ -41,6 +43,20 @@ Valid filters are:
|
||||
- `brackets`: Enclose value in brackets, e.g. 'value' => '[value]'
|
||||
- `shell_quote`: Quotes the value for safe usage in the shell, e.g. My file.jpeg => 'My file.jpeg'; only adds quotes if needed.
|
||||
- `function`: Run custom python function to filter value; use in format 'function:/path/to/file.py::function_name'. See example at https://github.com/RhetTbull/osxphotos/blob/master/examples/template_filter.py
|
||||
- `split(x)`: Split value into a list of values using x as delimiter, e.g. 'value1;value2' => ['value1', 'value2'] if used with split(;).
|
||||
- `autosplit`: Automatically split delimited string into separate values; will split strings delimited by comma, semicolon, or space, e.g. 'value1,value2' => ['value1', 'value2'].
|
||||
- `chop(x)`: Remove x characters off the end of value, e.g. chop(1): 'Value' => 'Valu'; when applied to a list, chops characters from each list value, e.g. chop(1): ['travel', 'beach']=> ['trave', 'beac'].
|
||||
- `chomp(x)`: Remove x characters from the beginning of value, e.g. chomp(1): ['Value'] => ['alue']; when applied to a list, removes characters from each list value, e.g. chomp(1): ['travel', 'beach']=> ['ravel', 'each'].
|
||||
- `sort`: Sort list of values, e.g. ['c', 'b', 'a'] => ['a', 'b', 'c'].
|
||||
- `rsort`: Sort list of values in reverse order, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
|
||||
- `reverse`: Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
|
||||
- `uniq`: Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b', 'c'].
|
||||
- `join(x)`: Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] => 'a,b,c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. e.g. join(): ['a', 'b', 'c'] => 'abc'.
|
||||
- `append(x)`: Append x to list of values, e.g. append(d): ['a', 'b', 'c'] => ['a', 'b', 'c', 'd'].
|
||||
- `prepend(x)`: Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] => ['d', 'a', 'b', 'c'].
|
||||
- `remove(x)`: Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] => ['a', 'c'].
|
||||
- `slice(start:stop:step)`: Slice list using same semantics as Python's list slicing, e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] => ['b', 'd']; slice(1:): ['a', 'b', 'c', 'd'] => ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c']; slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also sslice().
|
||||
- `sslice(start:stop:step)`: [s(tring) slice] Slice values in a list using same semantics as Python's string slicing, e.g. sslice(1:3):'abcd => 'bc'; sslice(1:4:2): 'abcd' => 'bd', etc. See also slice().
|
||||
|
||||
e.g. if Photo keywords are `["FOO","bar"]`:
|
||||
|
||||
@@ -53,8 +69,6 @@ e.g. if Photo description is "my description":
|
||||
|
||||
- `"{descr|titlecase}"` renders to: `"My Description"`
|
||||
|
||||
`(path_sep)`: optional path separator to use when joining path-like fields, for example `{folder_album}`. Default is "/".
|
||||
|
||||
e.g. If Photo is in `Album1` in `Folder1`:
|
||||
|
||||
- `"{folder_album}"` renders to `["Folder1/Album1"]`
|
||||
@@ -97,7 +111,7 @@ This can be used to rename files as well, for example:
|
||||
|
||||
This renames any photo that is a favorite as 'Favorite-ImageName.jpg' (where 'ImageName.jpg' is the original name of the photo) and all other photos with the unmodified original name.
|
||||
|
||||
`?bool_value`: Template fields may be evaluated as boolean (True/False) by appending "?" after the field name (and following "(path_sep)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is `"{hdr}"`) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is `"{title}"`) then the default value following a "," will be used.
|
||||
`?bool_value`: Template fields may be evaluated as boolean (True/False) by appending "?" after the field name (and following "(field_arg)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is `"{hdr}"`) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is `"{title}"`) then the default value following a "," will be used.
|
||||
|
||||
e.g. if photo is an HDR image,
|
||||
|
||||
@@ -127,3 +141,13 @@ Either or both bool_value or default (False value) may be empty which would resu
|
||||
If you want to include "{" or "}" in the output, use "{openbrace}" or "{closebrace}" template substitution.
|
||||
|
||||
e.g. `"{created.year}/{openbrace}{title}{closebrace}"` would result in `"2020/{Photo Title}"`.
|
||||
|
||||
**Variables**
|
||||
|
||||
You can define variables for later use in the template string using the format `{var:NAME,VALUE}`. Variables may then be referenced using the format `%NAME`. For example: `{var:foo,bar}` defines the variable `%foo` to have value `bar`. This can be useful if you want to re-use a complex template value in multiple places within your template string or for allowing the use of characters that would otherwise be prohibited in a template string. For example, the "pipe" (`|`) character is not allowed in a find/replace pair but you can get around this limitation like so: `{var:pipe,{pipe}}{title[-,%pipe]}` which replaces the `-` character with `|` (the value of `%pipe`).
|
||||
|
||||
Variables can also be referenced as fields in the template string, for example: `{var:year,created.year}{original_name}-{%year}`. In some cases, use of variables can make your template string more readable. Variables can be used as template fields, as values for filters, as values for conditional operations, or as default values. When used as a conditional value or default value, variables should be treated like any other field and enclosed in braces as conditional and default values are evaluated as template strings. For example: `{var:name,Katie}{person contains {%name}?{%name},Not-{%name}}`.
|
||||
|
||||
If you need to use a `%` (percent sign character), you can escape the percent sign by using `%%`. You can also use the `{percent}` template field where a template field is required. For example:
|
||||
|
||||
`{title[:,%%]}` replaces the `:` with `%` and `{title contains Foo?{title}{percent},{title}}` adds `%` to the title if it contains `Foo`.
|
||||
@@ -5,11 +5,12 @@ import datetime
|
||||
import locale
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from textx import TextXSyntaxError, metamodel_from_file
|
||||
|
||||
@@ -19,7 +20,7 @@ from .datetime_formatter import DateTimeFormatter
|
||||
from .exiftool import ExifToolCaching
|
||||
from .path_utils import sanitize_dirname, sanitize_filename, sanitize_pathpart
|
||||
from .text_detection import detect_text
|
||||
from .utils import expand_and_validate_filepath, load_function
|
||||
from .utils import expand_and_validate_filepath, load_function, uuid_to_shortuuid
|
||||
|
||||
__all__ = [
|
||||
"RenderOptions",
|
||||
@@ -140,6 +141,8 @@ TEMPLATE_SUBSTITUTIONS = {
|
||||
"{exif.lens_model}": "Lens model from original photo's EXIF information as imported by Photos, e.g. 'iPhone 6s back camera 4.15mm f/2.2'",
|
||||
"{moment}": "The moment title of the photo",
|
||||
"{uuid}": "Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'",
|
||||
"{shortuuid}": "A shorter representation of photo's internal universally unique identifier (UUID) for the photo, "
|
||||
+ "a 22-character string unique to the photo, e.g. 'JYsxugP9UjetmCbBCHXcmu'",
|
||||
"{id}": "A unique number for the photo based on its primary key in the Photos database. "
|
||||
+ "A sequential integer, e.g. 1, 2, 3...etc. Each asset associated with a photo (e.g. an image and Live Photo preview) will share the same id. "
|
||||
+ "May be formatted using a python string format code. "
|
||||
@@ -148,21 +151,23 @@ TEMPLATE_SUBSTITUTIONS = {
|
||||
"{album_seq}": "An integer, starting at 0, indicating the photo's index (sequence) in the containing album. "
|
||||
+ "Only valid when used in a '--filename' template and only when '{album}' or '{folder_album}' is used in the '--directory' template. "
|
||||
+ 'For example \'--directory "{folder_album}" --filename "{album_seq}_{original_name}"\'. '
|
||||
+ "To start counting at a value other than 0, append append a period and the starting value to the field name. "
|
||||
+ "For example, to start counting at 1 instead of 0: '{album_seq.1}'. "
|
||||
+ "To start counting at a value other than 0, append append '(starting_value)' to the field name. "
|
||||
+ "For example, to start counting at 1 instead of 0: '{album_seq(1)}'. "
|
||||
+ "May be formatted using a python string format code. "
|
||||
+ "For example, to format as a 5-digit integer and pad with zeros, use '{album_seq:05d}' which results in "
|
||||
+ "00000, 00001, 00002...etc. "
|
||||
+ "To format while also using a starting value: '{album_seq:05d(1)}' which results in 0001, 00002...etc."
|
||||
+ "This may result in incorrect sequences if you have duplicate albums with the same name; see also '{folder_album_seq}'.",
|
||||
"{folder_album_seq}": "An integer, starting at 0, indicating the photo's index (sequence) in the containing album and folder path. "
|
||||
+ "Only valid when used in a '--filename' template and only when '{folder_album}' is used in the '--directory' template. "
|
||||
+ 'For example \'--directory "{folder_album}" --filename "{folder_album_seq}_{original_name}"\'. '
|
||||
+ "To start counting at a value other than 0, append append a period and the starting value to the field name. "
|
||||
+ "For example, to start counting at 1 instead of 0: '{folder_album_seq.1}' "
|
||||
+ "To start counting at a value other than 0, append '(starting_value)' to the field name. "
|
||||
+ "For example, to start counting at 1 instead of 0: '{folder_album_seq(1)}' "
|
||||
+ "May be formatted using a python string format code. "
|
||||
+ "For example, to format as a 5-digit integer and pad with zeros, use '{folder_album_seq:05d}' which results in "
|
||||
+ "00000, 00001, 00002...etc. "
|
||||
+ "This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also '{album_seq}'.",
|
||||
+ "To format while also using a starting value: '{folder_album_seq:05d(1)}' which results in 0001, 00002...etc."
|
||||
+ "This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also '{album_seq}'. ",
|
||||
"{comma}": "A comma: ','",
|
||||
"{semicolon}": "A semicolon: ';'",
|
||||
"{questionmark}": "A question mark: '?'",
|
||||
@@ -222,6 +227,9 @@ TEMPLATE_SUBSTITUTIONS_MULTI_VALUED = {
|
||||
+ "Note: this feature is not the same thing as Live Text in macOS Monterey, which osxphotos does not yet support.",
|
||||
"{shell_quote}": "Use in form '{shell_quote,TEMPLATE}'; quotes the rendered TEMPLATE value(s) for safe usage in the shell, e.g. My file.jpeg => 'My file.jpeg'; only adds quotes if needed.",
|
||||
"{strip}": "Use in form '{strip,TEMPLATE}'; strips whitespace from begining and end of rendered TEMPLATE value(s).",
|
||||
"{format}": "Use in form, '{format:TYPE:FORMAT,TEMPLATE}'; converts TEMPLATE value to TYPE then formats the value "
|
||||
+ "using Python string formatting codes specified by FORMAT; TYPE is one of: 'int', 'float', or 'str'. "
|
||||
"For example, '{format:float:.1f,{exiftool:EXIF:FocalLength}}' will format focal length to 1 decimal place (e.g. '100.0'). ",
|
||||
"{function}": "Execute a python function from an external file and use return value as template substitution. "
|
||||
+ "Use in format: {function:file.py::function_name} where 'file.py' is the name of the python file and 'function_name' is the name of the function to call. "
|
||||
+ "The function will be passed the PhotoInfo object for the photo. "
|
||||
@@ -239,6 +247,27 @@ FILTER_VALUES = {
|
||||
"brackets": "Enclose value in brackets, e.g. 'value' => '[value]'",
|
||||
"shell_quote": "Quotes the value for safe usage in the shell, e.g. My file.jpeg => 'My file.jpeg'; only adds quotes if needed.",
|
||||
"function": "Run custom python function to filter value; use in format 'function:/path/to/file.py::function_name'. See example at https://github.com/RhetTbull/osxphotos/blob/master/examples/template_filter.py",
|
||||
"split(x)": "Split value into a list of values using x as delimiter, e.g. 'value1;value2' => ['value1', 'value2'] if used with split(;).",
|
||||
"autosplit": "Automatically split delimited string into separate values; will split strings delimited by comma, semicolon, or space, e.g. 'value1,value2' => ['value1', 'value2'].",
|
||||
"chop(x)": "Remove x characters off the end of value, e.g. chop(1): 'Value' => 'Valu'; when applied to a list, chops characters from each list value, e.g. chop(1): ['travel', 'beach']=> ['trave', 'beac'].",
|
||||
"chomp(x)": "Remove x characters from the beginning of value, e.g. chomp(1): ['Value'] => ['alue']; when applied to a list, removes characters from each list value, e.g. chomp(1): ['travel', 'beach']=> ['ravel', 'each'].",
|
||||
"sort": "Sort list of values, e.g. ['c', 'b', 'a'] => ['a', 'b', 'c'].",
|
||||
"rsort": "Sort list of values in reverse order, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].",
|
||||
"reverse": "Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].",
|
||||
"uniq": "Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b', 'c'].",
|
||||
"join(x)": "Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] => 'a,b,c'; "
|
||||
+ "the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters."
|
||||
+ "May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. "
|
||||
+ "e.g. join(): ['a', 'b', 'c'] => 'abc'.",
|
||||
"append(x)": "Append x to list of values, e.g. append(d): ['a', 'b', 'c'] => ['a', 'b', 'c', 'd'].",
|
||||
"prepend(x)": "Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] => ['d', 'a', 'b', 'c'].",
|
||||
"remove(x)": "Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] => ['a', 'c'].",
|
||||
"slice(start:stop:step)": "Slice list using same semantics as Python's list slicing, "
|
||||
+ "e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] => ['b', 'd']; "
|
||||
+ "slice(1:): ['a', 'b', 'c', 'd'] => ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c']; "
|
||||
+ "slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also sslice().",
|
||||
"sslice(start:stop:step)": "[s(tring) slice] Slice values in a list using same semantics as Python's string slicing, "
|
||||
+ "e.g. sslice(1:3):'abcd => 'bc'; sslice(1:4:2): 'abcd' => 'bd', etc. See also slice().",
|
||||
}
|
||||
|
||||
# Just the substitutions without the braces
|
||||
@@ -382,6 +411,7 @@ class PhotoTemplate:
|
||||
self.filepath = options.filepath
|
||||
self.quote = options.quote
|
||||
self.dest_path = options.dest_path
|
||||
self.variables = {}
|
||||
|
||||
def render(
|
||||
self,
|
||||
@@ -430,14 +460,13 @@ class PhotoTemplate:
|
||||
def _render_statement(
|
||||
self,
|
||||
statement,
|
||||
path_sep=None,
|
||||
field_arg=None,
|
||||
):
|
||||
path_sep = path_sep or self.path_sep
|
||||
results = []
|
||||
unmatched = []
|
||||
for ts in statement.template_strings:
|
||||
results, unmatched = self._render_template_string(
|
||||
ts, results=results, unmatched=unmatched, path_sep=path_sep
|
||||
ts, results=results, unmatched=unmatched, field_arg=field_arg
|
||||
)
|
||||
|
||||
rendered_strings = results
|
||||
@@ -457,7 +486,7 @@ class PhotoTemplate:
|
||||
def _render_template_string(
|
||||
self,
|
||||
ts,
|
||||
path_sep,
|
||||
field_arg,
|
||||
results=None,
|
||||
unmatched=None,
|
||||
):
|
||||
@@ -469,11 +498,6 @@ class PhotoTemplate:
|
||||
if ts.template:
|
||||
# have a template field to process
|
||||
field = ts.template.field
|
||||
field_part = field.split(".")[0]
|
||||
if field not in FIELD_NAMES and field_part not in FIELD_NAMES:
|
||||
unmatched.append(field)
|
||||
return [], unmatched
|
||||
|
||||
subfield = ts.template.subfield
|
||||
|
||||
# process filters
|
||||
@@ -481,14 +505,15 @@ class PhotoTemplate:
|
||||
if ts.template.filter is not None:
|
||||
filters = ts.template.filter.value
|
||||
|
||||
# process path_sep
|
||||
if ts.template.pathsep is not None:
|
||||
path_sep = ts.template.pathsep.value
|
||||
# process field arguments
|
||||
if ts.template.fieldarg is not None:
|
||||
field_arg = ts.template.fieldarg.value
|
||||
|
||||
# process delim
|
||||
if ts.template.delim is not None:
|
||||
# if value is None, means format was {+field}
|
||||
delim = ts.template.delim.value or ""
|
||||
delim = self.expand_variables_to_str(delim, "delim")
|
||||
else:
|
||||
delim = None
|
||||
|
||||
@@ -497,7 +522,7 @@ class PhotoTemplate:
|
||||
if ts.template.bool.value is not None:
|
||||
bool_val, u = self._render_statement(
|
||||
ts.template.bool.value,
|
||||
path_sep=path_sep,
|
||||
field_arg=field_arg,
|
||||
)
|
||||
unmatched.extend(u)
|
||||
else:
|
||||
@@ -513,7 +538,7 @@ class PhotoTemplate:
|
||||
if ts.template.default.value is not None:
|
||||
default, u = self._render_statement(
|
||||
ts.template.default.value,
|
||||
path_sep=path_sep,
|
||||
field_arg=field_arg,
|
||||
)
|
||||
unmatched.extend(u)
|
||||
else:
|
||||
@@ -528,11 +553,11 @@ class PhotoTemplate:
|
||||
negation = ts.template.conditional.negation
|
||||
if ts.template.conditional.value is not None:
|
||||
# conditional value is also a TemplateString
|
||||
conditional_value, u = self._render_statement(
|
||||
ts.template.conditional.value,
|
||||
path_sep=path_sep,
|
||||
)
|
||||
unmatched.extend(u)
|
||||
conditional_value = []
|
||||
for cv in ts.template.conditional.value:
|
||||
value, u = self._render_statement(cv)
|
||||
conditional_value += value
|
||||
unmatched.extend(u)
|
||||
else:
|
||||
# this shouldn't happen
|
||||
conditional_value = [""]
|
||||
@@ -541,43 +566,23 @@ class PhotoTemplate:
|
||||
negation = None
|
||||
conditional_value = []
|
||||
|
||||
vals = []
|
||||
if (
|
||||
field in SINGLE_VALUE_SUBSTITUTIONS
|
||||
or field.split(".")[0] in SINGLE_VALUE_SUBSTITUTIONS
|
||||
):
|
||||
vals = self.get_template_value(
|
||||
field,
|
||||
default=default,
|
||||
subfield=subfield,
|
||||
# delim=delim or self.inplace_sep,
|
||||
# path_sep=path_sep,
|
||||
)
|
||||
elif field == "exiftool":
|
||||
if subfield is None:
|
||||
raise ValueError(
|
||||
"SyntaxError: GROUP:NAME subfield must not be null with {exiftool:GROUP:NAME}'"
|
||||
if field.startswith("%"):
|
||||
# variable in form {%var}
|
||||
vals = self.variables.get(field[1:], None)
|
||||
if vals is None:
|
||||
raise SyntaxError(f"Variable '{field[1:]}' is not defined.")
|
||||
elif field == "var":
|
||||
if not subfield or not default:
|
||||
raise SyntaxError(
|
||||
"var must have a subfield and value in form {var:subfield,value}"
|
||||
)
|
||||
vals = self.get_template_value_exiftool(
|
||||
subfield,
|
||||
)
|
||||
elif field == "function":
|
||||
if subfield is None:
|
||||
raise ValueError(
|
||||
"SyntaxError: filename and function must not be null with {function::filename.py:function_name}"
|
||||
)
|
||||
vals = self.get_template_value_function(
|
||||
subfield,
|
||||
)
|
||||
elif field in MULTI_VALUE_SUBSTITUTIONS or field.startswith("photo"):
|
||||
vals = self.get_template_value_multi(
|
||||
field, subfield, path_sep=path_sep, default=default
|
||||
)
|
||||
elif field.split(".")[0] in PATHLIB_SUBSTITUTIONS:
|
||||
vals = self.get_template_value_pathlib(field)
|
||||
self.variables[subfield] = default
|
||||
vals = []
|
||||
else:
|
||||
unmatched.append(field)
|
||||
return [], unmatched
|
||||
vals, u = self.get_field_values(field, subfield, field_arg, default)
|
||||
if u:
|
||||
unmatched.extend(u)
|
||||
return [], unmatched
|
||||
|
||||
vals = [val for val in vals if val is not None]
|
||||
|
||||
@@ -586,7 +591,7 @@ class PhotoTemplate:
|
||||
vals = [sep.join(sorted(vals))] if vals else []
|
||||
|
||||
for filter_ in filters:
|
||||
vals = self.get_template_value_filter(filter_, vals)
|
||||
vals = self.get_filter_values(filter_, vals)
|
||||
|
||||
# process find/replace
|
||||
if ts.template.findreplace:
|
||||
@@ -594,7 +599,9 @@ class PhotoTemplate:
|
||||
for val in vals:
|
||||
for pair in ts.template.findreplace.pairs:
|
||||
find = pair.find or ""
|
||||
find = self.expand_variables_to_str(find, "find/replace")
|
||||
repl = pair.replace or ""
|
||||
repl = self.expand_variables_to_str(repl, "find/replace")
|
||||
val = val.replace(find, repl)
|
||||
new_vals.append(val)
|
||||
vals = new_vals
|
||||
@@ -621,22 +628,23 @@ class PhotoTemplate:
|
||||
|
||||
def comparison_test(test_function):
|
||||
"""Perform numerical comparisons using test_function; closure to capture conditional_val, vals, negation"""
|
||||
if len(vals) != 1 or len(conditional_value) != 1:
|
||||
raise ValueError(
|
||||
f"comparison operators may only be used with a single value: {vals} {conditional_value}"
|
||||
# returns True if any of the values match the condition
|
||||
if len(conditional_value) != 1:
|
||||
raise SyntaxError(
|
||||
f"comparison operators may only be used with a single conditional value: {conditional_value}"
|
||||
)
|
||||
try:
|
||||
match = bool(
|
||||
test_function(float(vals[0]), float(conditional_value[0]))
|
||||
match = any(
|
||||
bool(test_function(float(v), float(conditional_value[0])))
|
||||
for v in vals
|
||||
)
|
||||
|
||||
return (
|
||||
["True"]
|
||||
if (match and not negation) or (negation and not match)
|
||||
else []
|
||||
)
|
||||
except ValueError as e:
|
||||
raise ValueError(
|
||||
raise SyntaxError(
|
||||
f"comparison operators may only be used with values that can be converted to numbers: {vals} {conditional_value}"
|
||||
) from e
|
||||
|
||||
@@ -678,7 +686,8 @@ class PhotoTemplate:
|
||||
|
||||
if is_bool:
|
||||
vals = default if not vals else bool_val
|
||||
elif not vals:
|
||||
elif not vals and field != "var":
|
||||
# don't assign default value if the template was variable assignment
|
||||
vals = default or [self.none_str]
|
||||
|
||||
pre = ts.pre or ""
|
||||
@@ -700,14 +709,103 @@ class PhotoTemplate:
|
||||
|
||||
return results, unmatched
|
||||
|
||||
def expand_variables_to_str(self, value: str, name: str) -> str:
|
||||
"""
|
||||
Expand variables in value and return a str of the expanded value.
|
||||
Enforce that the expanded value is a single value, raises ValueError if not.
|
||||
|
||||
Args:
|
||||
value: the value to expand
|
||||
name: the name of the value being expanded (used in error messages)
|
||||
"""
|
||||
expanded = self.expand_variables(value)
|
||||
if len(expanded) != 1:
|
||||
raise SyntaxError(f"{name} must have a single value, not {expanded}")
|
||||
return expanded[0]
|
||||
|
||||
def expand_variables(self, value: str) -> List[str]:
|
||||
"""Expand variables in value"""
|
||||
# replace any variables with their values
|
||||
values = [value]
|
||||
new_values = []
|
||||
# allow %% to escape %, match variables in form %var
|
||||
variable_match = re.compile(r"(?:%%)*(%[\w]+)?")
|
||||
while True:
|
||||
for value in values:
|
||||
match = variable_match.search(value)
|
||||
if not match or not match[1]:
|
||||
break
|
||||
var = match[1]
|
||||
var_name = var[1:]
|
||||
if var_name not in self.variables:
|
||||
raise SyntaxError(f"Variable '{var_name}' is not defined.")
|
||||
for val in values:
|
||||
for var_val in self.variables[var_name]:
|
||||
new_values.append(
|
||||
re.sub(f"(%%)*{var}", r"\g<1>" + var_val, val)
|
||||
)
|
||||
if new_values == values or not new_values:
|
||||
break
|
||||
values = new_values.copy()
|
||||
new_values = []
|
||||
|
||||
# replace %% with %
|
||||
# any %% left in the string will be replaced with %
|
||||
values = [value.replace("%%", "%") for value in values]
|
||||
|
||||
return values
|
||||
|
||||
def get_field_values(
|
||||
self,
|
||||
field: str,
|
||||
subfield: Optional[str],
|
||||
field_arg: Optional[str],
|
||||
default: List[str],
|
||||
) -> Tuple[List[str], List[str]]:
|
||||
"""Get the values for a field"""
|
||||
vals = []
|
||||
unmatched = []
|
||||
if (
|
||||
field in SINGLE_VALUE_SUBSTITUTIONS
|
||||
or field.split(".")[0] in SINGLE_VALUE_SUBSTITUTIONS
|
||||
):
|
||||
vals = self.get_template_value(
|
||||
field,
|
||||
default=default,
|
||||
subfield=subfield,
|
||||
field_arg=field_arg,
|
||||
)
|
||||
elif field == "exiftool":
|
||||
if subfield is None:
|
||||
raise ValueError(
|
||||
"SyntaxError: GROUP:NAME subfield must not be null with {exiftool:GROUP:NAME}'"
|
||||
)
|
||||
vals = self.get_template_value_exiftool(
|
||||
subfield,
|
||||
)
|
||||
elif field == "function":
|
||||
if subfield is None:
|
||||
raise ValueError(
|
||||
"SyntaxError: filename and function must not be null with {function::filename.py:function_name}"
|
||||
)
|
||||
vals = self.get_template_value_function(subfield, field_arg)
|
||||
elif field in MULTI_VALUE_SUBSTITUTIONS or field.startswith("photo"):
|
||||
vals = self.get_template_value_multi(
|
||||
field, subfield, path_sep=field_arg, default=default
|
||||
)
|
||||
elif field.split(".")[0] in PATHLIB_SUBSTITUTIONS:
|
||||
vals = self.get_template_value_pathlib(field)
|
||||
else:
|
||||
unmatched.append(field)
|
||||
return [], unmatched
|
||||
return vals, unmatched
|
||||
|
||||
def get_template_value(
|
||||
self,
|
||||
field,
|
||||
default,
|
||||
subfield=None,
|
||||
# bool_val=None,
|
||||
# delim=None,
|
||||
# path_sep=None,
|
||||
subfield,
|
||||
field_arg,
|
||||
):
|
||||
"""lookup value for template field (single-value template substitutions)
|
||||
|
||||
@@ -991,6 +1089,8 @@ class PhotoTemplate:
|
||||
value = self.photo.moment_info.title if self.photo.moment_info else None
|
||||
elif field == "uuid":
|
||||
value = self.photo.uuid
|
||||
elif field == "shortuuid":
|
||||
value = uuid_to_shortuuid(self.photo.uuid) if self.photo.uuid else None
|
||||
elif field == "id":
|
||||
value = format_str_value(self.photo._info["pk"], subfield)
|
||||
elif field.startswith("album_seq") or field.startswith("folder_album_seq"):
|
||||
@@ -1004,9 +1104,8 @@ class PhotoTemplate:
|
||||
else:
|
||||
value = None
|
||||
if value is not None:
|
||||
with suppress(IndexError):
|
||||
start_id = field.split(".", 1)
|
||||
value = int(value) + int(start_id[1])
|
||||
start_id = int(field_arg) if field_arg is not None else 0
|
||||
value = int(value) + start_id
|
||||
value = format_str_value(value, subfield)
|
||||
else:
|
||||
# if here, didn't get a match
|
||||
@@ -1054,54 +1153,121 @@ class PhotoTemplate:
|
||||
|
||||
return [value]
|
||||
|
||||
def get_template_value_filter(self, filter_, values):
|
||||
def get_filter_values(self, filter_: str, values: List[str]) -> List[str]:
|
||||
"""Return filtered values"""
|
||||
|
||||
# extract args, if any
|
||||
if re.search(r"\(.*\)", filter_):
|
||||
filter_, args = filter_.split("(", 1)
|
||||
args = args.rstrip(")")
|
||||
args = self.expand_variables_to_str(args, "Filter arguments")
|
||||
else:
|
||||
args = None
|
||||
|
||||
# check that filter name (without subfields or arguments) is valid
|
||||
valid_filters = [f.split("(")[0] for f in FILTER_VALUES]
|
||||
if filter_.split(":")[0] not in valid_filters:
|
||||
raise SyntaxError(f"Unknown filter: {filter_}")
|
||||
|
||||
if filter_ in [
|
||||
"split",
|
||||
"chop",
|
||||
"chomp",
|
||||
"append",
|
||||
"prepend",
|
||||
"remove",
|
||||
"slice",
|
||||
"sslice",
|
||||
] and (args is None or not len(args)):
|
||||
raise SyntaxError(f"{filter_} requires arguments")
|
||||
|
||||
if filter_ == "lower":
|
||||
if values and type(values) == list:
|
||||
value = [v.lower() for v in values]
|
||||
else:
|
||||
value = [values.lower()] if values else []
|
||||
value = [v.lower() for v in values]
|
||||
elif filter_ == "upper":
|
||||
if values and type(values) == list:
|
||||
value = [v.upper() for v in values]
|
||||
else:
|
||||
value = [values.upper()] if values else []
|
||||
value = [v.upper() for v in values]
|
||||
elif filter_ == "strip":
|
||||
if values and type(values) == list:
|
||||
value = [v.strip() for v in values]
|
||||
else:
|
||||
value = [values.strip()] if values else []
|
||||
value = [v.strip() for v in values]
|
||||
elif filter_ == "capitalize":
|
||||
if values and type(values) == list:
|
||||
value = [v.capitalize() for v in values]
|
||||
else:
|
||||
value = [values.capitalize()] if values else []
|
||||
value = [v.capitalize() for v in values]
|
||||
elif filter_ == "titlecase":
|
||||
if values and type(values) == list:
|
||||
value = [v.title() for v in values]
|
||||
else:
|
||||
value = [values.title()] if values else []
|
||||
value = [v.title() for v in values]
|
||||
elif filter_ == "braces":
|
||||
if values and type(values) == list:
|
||||
value = ["{" + v + "}" for v in values]
|
||||
else:
|
||||
value = ["{" + values + "}"] if values else []
|
||||
value = ["{" + v + "}" for v in values]
|
||||
elif filter_ == "parens":
|
||||
if values and type(values) == list:
|
||||
value = [f"({v})" for v in values]
|
||||
else:
|
||||
value = [f"({values})"] if values else []
|
||||
value = ["(" + v + ")" for v in values]
|
||||
elif filter_ == "brackets":
|
||||
if values and type(values) == list:
|
||||
value = [f"[{v}]" for v in values]
|
||||
else:
|
||||
value = [f"[{values}]"] if values else []
|
||||
value = ["[" + v + "]" for v in values]
|
||||
elif filter_ == "shell_quote":
|
||||
if values and type(values) == list:
|
||||
value = [shlex.quote(v) for v in values]
|
||||
value = [shlex.quote(v) for v in values]
|
||||
elif filter_ == "split":
|
||||
# split on delimiter
|
||||
delim = args
|
||||
if delim:
|
||||
new_values = []
|
||||
for v in values:
|
||||
new_values.extend(v.split(delim))
|
||||
value = new_values
|
||||
else:
|
||||
value = [shlex.quote(values)] if values else []
|
||||
value = values
|
||||
elif filter_ == "chop":
|
||||
# chop off characters from the end
|
||||
try:
|
||||
chop = int(args)
|
||||
except ValueError:
|
||||
raise SyntaxError(f"Invalid value for chop: {args}")
|
||||
value = [v[:-chop] for v in values] if chop else values
|
||||
elif filter_ == "chomp":
|
||||
# chop off characters from the beginning
|
||||
try:
|
||||
chomp = int(args)
|
||||
except ValueError:
|
||||
raise SyntaxError(f"Invalid value for chomp: {args}")
|
||||
value = [v[chomp:] for v in values] if chomp else values
|
||||
elif filter_ == "autosplit":
|
||||
# try to split keyword strings automatically
|
||||
temp_values = [v.replace(",", " ") for v in values]
|
||||
temp_values = [v.replace(";", " ") for v in temp_values]
|
||||
value = []
|
||||
for val in temp_values:
|
||||
value.extend(val.split())
|
||||
elif filter_ == "sort":
|
||||
# sort list of values
|
||||
value = sorted(values)
|
||||
elif filter_ == "rsort":
|
||||
# reverse sort list of values
|
||||
value = sorted(values, reverse=True)
|
||||
elif filter_ == "reverse":
|
||||
# reverse list of values
|
||||
value = values[::-1]
|
||||
elif filter_ == "uniq":
|
||||
# remove duplicate values from list
|
||||
temp_values = []
|
||||
for v in values:
|
||||
if v not in temp_values:
|
||||
temp_values.append(v)
|
||||
value = temp_values
|
||||
elif filter_ == "join":
|
||||
# join list of values with delimiter
|
||||
delim = args or ""
|
||||
value = [delim.join(values)]
|
||||
elif filter_ == "append":
|
||||
# append value to list
|
||||
value = values + [args]
|
||||
elif filter_ == "prepend":
|
||||
# prepend value to list
|
||||
value = [args] + values
|
||||
elif filter_ == "remove":
|
||||
# remove value from list
|
||||
value = [v for v in values if v != args]
|
||||
elif filter_ == "slice":
|
||||
# slice list of values
|
||||
value = values[create_slice(args)]
|
||||
elif filter_ == "sslice":
|
||||
# slice each value in a list
|
||||
slice_ = create_slice(args)
|
||||
value = [v[slice_] for v in values]
|
||||
elif filter_.startswith("function:"):
|
||||
value = self.get_template_value_filter_function(filter_, values)
|
||||
value = self.get_template_value_filter_function(filter_, args, values)
|
||||
else:
|
||||
value = []
|
||||
return value
|
||||
@@ -1124,6 +1290,8 @@ class PhotoTemplate:
|
||||
|
||||
""" return list of values for a multi-valued template field """
|
||||
|
||||
path_sep = path_sep or self.path_sep
|
||||
|
||||
if self.photo.uuid is None:
|
||||
return []
|
||||
|
||||
@@ -1189,6 +1357,8 @@ class PhotoTemplate:
|
||||
values = [shlex.quote(v) for v in default if v]
|
||||
elif field == "strip":
|
||||
values = [v.strip() for v in default]
|
||||
elif field == "format":
|
||||
values = self.get_format_values(field, subfield, default)
|
||||
elif field.startswith("photo"):
|
||||
# provide access to PhotoInfo object
|
||||
properties = field.split(".")
|
||||
@@ -1203,10 +1373,11 @@ class PhotoTemplate:
|
||||
obj = getattr(obj, property_)
|
||||
if obj is None:
|
||||
break
|
||||
except AttributeError:
|
||||
except AttributeError as e:
|
||||
raise ValueError(
|
||||
"Invalid property for {photo} template: " + f"'{property_}'"
|
||||
)
|
||||
) from e
|
||||
|
||||
if obj is None:
|
||||
values = []
|
||||
elif isinstance(obj, bool):
|
||||
@@ -1231,6 +1402,31 @@ class PhotoTemplate:
|
||||
values = values or []
|
||||
return values
|
||||
|
||||
def get_format_values(
|
||||
self, field: str, subfield: str, default: List[str]
|
||||
) -> Optional[List[Optional[str]]]:
|
||||
"""Return values for {format} templates"""
|
||||
|
||||
if field != "format":
|
||||
raise ValueError(f"Unhandled template value in get_format_values: {field}")
|
||||
|
||||
if not subfield or ":" not in subfield:
|
||||
raise SyntaxError("{format} requires subfield in form TYPE:FORMAT")
|
||||
type_, format_str = subfield.split(":", 1)
|
||||
if type_ not in ("int", "float", "str"):
|
||||
raise SyntaxError(
|
||||
f"'{type_}' is not a valid type for {format}: must be one of 'int', 'float', 'str'"
|
||||
)
|
||||
if type_ == "int":
|
||||
# convert to float then int to avoid error when converting a string float to int
|
||||
default_ = [int(float(v)) for v in default]
|
||||
elif type_ == "float":
|
||||
default_ = [float(v) for v in default]
|
||||
else:
|
||||
default_ = default
|
||||
format_str = self.expand_variables_to_str(format_str, "format string")
|
||||
return [format_str_value(v, format_str) for v in default_]
|
||||
|
||||
def get_template_value_exiftool(
|
||||
self,
|
||||
subfield,
|
||||
@@ -1264,6 +1460,7 @@ class PhotoTemplate:
|
||||
def get_template_value_function(
|
||||
self,
|
||||
subfield,
|
||||
field_arg,
|
||||
):
|
||||
"""Get template value from external function"""
|
||||
|
||||
@@ -1279,7 +1476,13 @@ class PhotoTemplate:
|
||||
raise ValueError(f"'{filename}' does not appear to be a file")
|
||||
|
||||
template_func = load_function(filename_validated, funcname)
|
||||
values = template_func(self.photo, options=self.options)
|
||||
if self.photo.uuid is None:
|
||||
# must be a PhotoInfoNone instance
|
||||
# if no uuid, then template is being validated but not actually run
|
||||
# so don't run the function
|
||||
values = []
|
||||
else:
|
||||
values = template_func(self.photo, options=self.options, args=field_arg)
|
||||
|
||||
if not isinstance(values, (str, list)):
|
||||
raise TypeError(
|
||||
@@ -1297,8 +1500,9 @@ class PhotoTemplate:
|
||||
|
||||
return values
|
||||
|
||||
def get_template_value_filter_function(self, filter_, values):
|
||||
def get_template_value_filter_function(self, filter_, args, values):
|
||||
"""Filter template value from external function"""
|
||||
# TODO: add args to filter function call? Would change signature of function
|
||||
|
||||
filter_ = filter_.replace("function:", "")
|
||||
|
||||
@@ -1317,7 +1521,11 @@ class PhotoTemplate:
|
||||
|
||||
if not isinstance(values, (list, tuple)):
|
||||
values = [values]
|
||||
values = template_func(values)
|
||||
|
||||
if self.photo.uuid is not None:
|
||||
# if uuid is None, it's a PhotoInfoNone instance and template is being validated
|
||||
# so don't run the function
|
||||
values = template_func(values)
|
||||
|
||||
if not isinstance(values, list):
|
||||
raise TypeError(
|
||||
@@ -1329,10 +1537,7 @@ class PhotoTemplate:
|
||||
def get_photo_video_type(self, default):
|
||||
"""return media type, e.g. photo or video"""
|
||||
default_dict = parse_default_kv(default, PHOTO_VIDEO_TYPE_DEFAULTS)
|
||||
if self.photo.isphoto:
|
||||
return default_dict["photo"]
|
||||
else:
|
||||
return default_dict["video"]
|
||||
return default_dict["photo"] if self.photo.isphoto else default_dict["video"]
|
||||
|
||||
def get_media_type(self, default):
|
||||
"""return special media type, e.g. slow_mo, panorama, etc., defaults to photo or video if no special type"""
|
||||
@@ -1360,12 +1565,8 @@ class PhotoTemplate:
|
||||
return default_dict["photo"]
|
||||
|
||||
def get_photo_bool_attribute(self, attr, default, bool_val):
|
||||
# get value for a PhotoInfo bool attribute
|
||||
val = getattr(self.photo, attr)
|
||||
if val:
|
||||
return bool_val
|
||||
else:
|
||||
return default
|
||||
"""Return the boolean value for a photo attribute"""
|
||||
return bool_val if (val := getattr(self.photo, attr)) else default
|
||||
|
||||
|
||||
def parse_default_kv(default, default_dict):
|
||||
@@ -1489,3 +1690,25 @@ def _get_detected_text(photo, confidence=TEXT_DETECTION_CONFIDENCE_THRESHOLD):
|
||||
# so the first time this gets called is slow but repeated accesses are fast
|
||||
detected_text = photo._detected_text()
|
||||
return [text for text, conf in detected_text if conf >= confidence]
|
||||
|
||||
|
||||
def create_slice(args):
|
||||
"""Create a slice object from a string of args in form "start:end:step" """
|
||||
slice_args = args.split(":")
|
||||
if len(slice_args) == 1:
|
||||
start = int(slice_args[0] or 0)
|
||||
end = None
|
||||
step = None
|
||||
elif len(slice_args) == 2:
|
||||
start, end = slice_args
|
||||
start = int(start) if start != "" else None
|
||||
end = int(end) if end != "" else None
|
||||
step = None
|
||||
elif len(slice_args) == 3:
|
||||
start, end, step = slice_args
|
||||
start = int(start) if start != "" else None
|
||||
end = int(end) if end != "" else None
|
||||
step = int(step) if step != "" else None
|
||||
else:
|
||||
raise SyntaxError(f"Invalid slice: {args}")
|
||||
return slice(start, end, step)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// OSXPhotos Metadata Template Language (MTL)
|
||||
// a TemplateString has format:
|
||||
// pre{delim+template_field:subfield|filter(path_sep)[find,replace] conditional?bool_value,default}post
|
||||
// pre{delim+template_field:subfield(field_arg)|filter[find,replace] conditional?bool_value,default}post
|
||||
// a TemplateStatement may contain zero or more TemplateStrings
|
||||
// The pre and post are optional strings
|
||||
// The template itself (inside the {}) is also optional but if present
|
||||
@@ -22,8 +22,8 @@ Template:
|
||||
delim=Delim
|
||||
field=Field
|
||||
subfield=SubField
|
||||
fieldarg=FieldArg
|
||||
filter=Filter
|
||||
pathsep=PathSep
|
||||
findreplace=FindReplace
|
||||
conditional=Conditional
|
||||
bool=Boolean
|
||||
@@ -52,7 +52,7 @@ Field:
|
||||
;
|
||||
|
||||
FIELD_WORD:
|
||||
/[\.\w]+/
|
||||
/[\%]?[\.\w]+/
|
||||
;
|
||||
|
||||
SubField:
|
||||
@@ -70,21 +70,22 @@ SUBFIELD_WORD:
|
||||
Filter:
|
||||
(
|
||||
"|"-
|
||||
(value+=FILTER_WORD['|'])?
|
||||
(value+=FILTER_FUNCTION['|'])?
|
||||
)?
|
||||
;
|
||||
|
||||
FILTER_WORD:
|
||||
/[\.\w:\/]+/
|
||||
FILTER_FUNCTION:
|
||||
/[\.\w:\/]+(\([^\)]*\))?/
|
||||
;
|
||||
|
||||
|
||||
Conditional:
|
||||
(
|
||||
(" "+)-
|
||||
(negation=NEGATION)?
|
||||
(operator=OPERATOR)
|
||||
(" "+)-
|
||||
(value=Statement)
|
||||
(value+=Statement['|'])
|
||||
)?
|
||||
;
|
||||
|
||||
@@ -96,7 +97,7 @@ OPERATOR:
|
||||
"contains" | "matches" | "startswith" | "endswith" | "<=" | ">=" | "<" | ">" | "==" | "!="
|
||||
;
|
||||
|
||||
PathSep:
|
||||
FieldArg:
|
||||
(
|
||||
"("
|
||||
(value=/[^\(\)\{\}]+/)?
|
||||
|
||||
@@ -32,7 +32,7 @@ def detect_text(img_path: str, orientation: Optional[int] = None) -> List:
|
||||
orientation: optional EXIF orientation (if known, passing orientation may improve quality of results)
|
||||
"""
|
||||
if not vision:
|
||||
logging.warning(f"detect_text not implemented for this version of macOS")
|
||||
logging.warning("detect_text not implemented for this version of macOS")
|
||||
return []
|
||||
|
||||
with objc.autorelease_pool():
|
||||
@@ -47,18 +47,18 @@ def detect_text(img_path: str, orientation: Optional[int] = None) -> List:
|
||||
input_image = Quartz.CIImage.imageWithContentsOfURL_(input_url)
|
||||
|
||||
vision_options = NSDictionary.dictionaryWithDictionary_({})
|
||||
if orientation is not None:
|
||||
if not 1 <= orientation <= 8:
|
||||
raise ValueError("orientation must be between 1 and 8")
|
||||
vision_handler = Vision.VNImageRequestHandler.alloc().initWithCIImage_orientation_options_(
|
||||
input_image, orientation, vision_options
|
||||
)
|
||||
else:
|
||||
if orientation is None:
|
||||
vision_handler = (
|
||||
Vision.VNImageRequestHandler.alloc().initWithCIImage_options_(
|
||||
input_image, vision_options
|
||||
)
|
||||
)
|
||||
elif 1 <= orientation <= 8:
|
||||
vision_handler = Vision.VNImageRequestHandler.alloc().initWithCIImage_orientation_options_(
|
||||
input_image, orientation, vision_options
|
||||
)
|
||||
else:
|
||||
raise ValueError("orientation must be between 1 and 8")
|
||||
results = []
|
||||
handler = make_request_handler(results)
|
||||
vision_request = (
|
||||
|
||||
@@ -178,6 +178,14 @@ A powerful feature of Photos is that it uses machine learning algorithms to auto
|
||||
|
||||
`osxphotos export /path/to/export --exiftool --keyword-template "{label}"`
|
||||
|
||||
## Removing a keyword during export
|
||||
|
||||
If some of your photos contain a keyword you do not want to be added to the exported file with `--exiftool`, you can use the template system to remove the keyword from the exported file. For example, if you want to remove the keyword "MyKeyword" from all your photos:
|
||||
|
||||
`osxphotos export /path/to/export --exiftool --keyword-template "{keyword|remove(MyKeyword)}" --replace-keywords`
|
||||
|
||||
In this example, `|remove(MyKeyword)` is a filter which removes `MyKeyword` from the keyword list of every photo being processed. The `--replace-keywords` option instructs osxphotos to replace the keywords in the exported file with the filtered keywords from `--keyword-template`.
|
||||
|
||||
**Note**: When evaluating templates for `--directory` and `--filename`, osxphotos inserts the automatic default value "_" for any template field which is null (empty or blank). This is to ensure that there's never a null directory or filename created. For metadata templates such as `--keyword-template`, osxphotos does not provide an automatic default value thus if the template field is null, no keyword would be created. Of course, you can provide a default value if desired and osxphotos will use this. For example, to add "nolabel" as a keyword for any photo that doesn't have labels:
|
||||
|
||||
`osxphotos export /path/to/export --exiftool --keyword-template "{label,nolabel}"`
|
||||
|
||||
@@ -19,9 +19,11 @@ import unicodedata
|
||||
import urllib.parse
|
||||
from plistlib import load as plistload
|
||||
from typing import Callable, List, Optional, Tuple, Union
|
||||
from uuid import UUID
|
||||
|
||||
import CoreFoundation
|
||||
import requests
|
||||
import shortuuid
|
||||
|
||||
from ._constants import UNICODE_FORMAT
|
||||
|
||||
@@ -41,6 +43,8 @@ __all__ = [
|
||||
"normalize_fs_path",
|
||||
"normalize_unicode",
|
||||
"pluralize",
|
||||
"shortuuid_to_uuid",
|
||||
"uuid_to_shortuuid",
|
||||
]
|
||||
|
||||
|
||||
@@ -411,7 +415,7 @@ def normalize_unicode(value):
|
||||
|
||||
def increment_filename_with_count(
|
||||
filepath: Union[str, pathlib.Path], count: int = 0
|
||||
) -> str:
|
||||
) -> Tuple[str, int]:
|
||||
"""Return filename (1).ext, etc if filename.ext exists
|
||||
|
||||
If file exists in filename's parent folder with same stem as filename,
|
||||
@@ -447,6 +451,7 @@ def increment_filename(filepath: Union[str, pathlib.Path]) -> str:
|
||||
|
||||
Args:
|
||||
filepath: str or pathlib.Path; full path, including file name
|
||||
force: force the file count to increment by at least 1 even if filepath doesn't exist
|
||||
|
||||
Returns:
|
||||
new filepath (or same if not incremented)
|
||||
@@ -457,6 +462,13 @@ def increment_filename(filepath: Union[str, pathlib.Path]) -> str:
|
||||
return new_filepath
|
||||
|
||||
|
||||
def extract_increment_count_from_filename(filepath: Union[str, pathlib.Path]) -> int:
|
||||
"""Extract a count from end of file name if it exists or 0 if not; count takes forms file (1).ext, file (2).ext, etc."""
|
||||
filepath = str(filepath)
|
||||
match = re.search(r"(?s:.*)\((\d+)\)", filepath)
|
||||
return int(match[1]) if match else 0
|
||||
|
||||
|
||||
def expand_and_validate_filepath(path: str) -> str:
|
||||
"""validate and expand ~ in filepath, also un-escapes spaces
|
||||
|
||||
@@ -526,3 +538,13 @@ def hexdigest(strval: str) -> str:
|
||||
h = hashlib.blake2b(digest_size=20)
|
||||
h.update(bytes(strval, "utf-8"))
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def uuid_to_shortuuid(uuid: str) -> str:
|
||||
"""Convert uuid to shortuuid"""
|
||||
return str(shortuuid.encode(UUID(uuid)))
|
||||
|
||||
|
||||
def shortuuid_to_uuid(short_uuid: str) -> str:
|
||||
"""Convert shortuuid to uuid"""
|
||||
return str(shortuuid.decode(short_uuid)).upper()
|
||||
|
||||
@@ -24,6 +24,7 @@ PyYAML>=5.4.1,<6.0.0
|
||||
requests>=2.27.1,<3.0.0
|
||||
rich_theme_manager>=0.11.0
|
||||
rich>=11.2.0,<13.0.0
|
||||
shortuuid==1.0.9
|
||||
tenacity>=8.0.1,<9.0.0
|
||||
textx>=2.3.0,<2.4.0
|
||||
toml>=0.10.2,<0.11.0
|
||||
|
||||
1
setup.py
@@ -99,6 +99,7 @@ setup(
|
||||
"requests>=2.27.1,<3.0.0",
|
||||
"rich>=11.2.0,<13.0.0",
|
||||
"rich_theme_manager>=0.11.0",
|
||||
"shortuuid==1.0.9",
|
||||
"tenacity>=8.0.1,<9.0.0",
|
||||
"textx>=2.3.0,<3.0.0",
|
||||
"toml>=0.10.2,<0.11.0",
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LibrarySchemaVersion</key>
|
||||
<integer>5001</integer>
|
||||
<key>MetaSchemaVersion</key>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>hostname</key>
|
||||
<string>tc1.local</string>
|
||||
<key>hostuuid</key>
|
||||
<string>A22F6630-733E-54D7-99F1-B3805B38E11B</string>
|
||||
<key>pid</key>
|
||||
<integer>548</integer>
|
||||
<key>processname</key>
|
||||
<string>photolibraryd</string>
|
||||
<key>uid</key>
|
||||
<integer>501</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>insertAlbum</key>
|
||||
<array/>
|
||||
<key>insertAsset</key>
|
||||
<array/>
|
||||
<key>insertHighlight</key>
|
||||
<array/>
|
||||
<key>insertMemory</key>
|
||||
<array/>
|
||||
<key>insertMoment</key>
|
||||
<array/>
|
||||
<key>removeAlbum</key>
|
||||
<array/>
|
||||
<key>removeAsset</key>
|
||||
<array/>
|
||||
<key>removeHighlight</key>
|
||||
<array/>
|
||||
<key>removeMemory</key>
|
||||
<array/>
|
||||
<key>removeMoment</key>
|
||||
<array/>
|
||||
<key>renamePerson</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>embeddingVersion</key>
|
||||
<string>1</string>
|
||||
<key>featureFlags</key>
|
||||
<string>63</string>
|
||||
<key>featuredContentAllowed</key>
|
||||
<string>1</string>
|
||||
<key>localeIdentifier</key>
|
||||
<string>en_GB</string>
|
||||
<key>sceneTaxonomySHA</key>
|
||||
<string>35f76559cb0770342bcea46fce0e3f562844ab7506e0e4b4ad1256abb0a07be2,4afa5d3c45c08a664cf73cff957aaeeae3a325d2970aada51268407b9ad0f03e</string>
|
||||
<key>searchIndexVersion</key>
|
||||
<string>16016</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
After Width: | Height: | Size: 574 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 500 KiB |
|
After Width: | Height: | Size: 524 KiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 528 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 450 KiB |
|
After Width: | Height: | Size: 541 KiB |
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>MigrationService</key>
|
||||
<dict>
|
||||
<key>State</key>
|
||||
<integer>4</integer>
|
||||
</dict>
|
||||
<key>MigrationService.LastCompletedTask</key>
|
||||
<integer>12</integer>
|
||||
<key>MigrationService.ValidationCounts</key>
|
||||
<dict>
|
||||
<key>MigrationDetectedFaceprint</key>
|
||||
<integer>6</integer>
|
||||
<key>MigrationManagedAsset</key>
|
||||
<integer>0</integer>
|
||||
<key>MigrationSceneClassification</key>
|
||||
<integer>44</integer>
|
||||
<key>MigrationUnmanagedAdjustment</key>
|
||||
<integer>0</integer>
|
||||
<key>RDVersion.cloudLocalState.CPLIsNotPushed</key>
|
||||
<integer>7</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CollapsedSidebarSectionIdentifiers</key>
|
||||
<array/>
|
||||
<key>ExpandedSidebarItemIdentifiers</key>
|
||||
<array>
|
||||
<string>92D68107-B6C7-453B-96D2-97B0F26D5B8B/L0/020</string>
|
||||
<string>88A5F8B8-5B9A-43C7-BB85-3952B81580EB/L0/020</string>
|
||||
<string>29EF7A97-7E76-4D5F-A5E0-CC0A93E8524C/L0/020</string>
|
||||
<string>2C2AF115-BD1D-4434-A747-D1C8BD8E2045/L0/020</string>
|
||||
<string>CB051A4C-2CB7-4B90-B59B-08CC4D0C2823/L0/020</string>
|
||||
</array>
|
||||
<key>IPXWorkspaceControllerZoomLevelsKey</key>
|
||||
<dict>
|
||||
<key>kZoomLevelIdentifierPhotosGrid</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
<key>Photos</key>
|
||||
<dict>
|
||||
<key>CollapsedSidebarSectionIdentifiers</key>
|
||||
<array/>
|
||||
<key>ExpandedSidebarItemIdentifiers</key>
|
||||
<array>
|
||||
<string>TopLevelAlbums</string>
|
||||
<string>TopLevelSlideshows</string>
|
||||
</array>
|
||||
<key>IPXWorkspaceControllerZoomLevelsKey</key>
|
||||
<dict>
|
||||
<key>kZoomLevelIdentifierAlbums</key>
|
||||
<integer>7</integer>
|
||||
<key>kZoomLevelIdentifierVersions</key>
|
||||
<integer>7</integer>
|
||||
</dict>
|
||||
<key>lastAddToDestination</key>
|
||||
<dict>
|
||||
<key>key</key>
|
||||
<integer>1</integer>
|
||||
<key>lastKnownDisplayName</key>
|
||||
<string>September 28, 2018</string>
|
||||
<key>type</key>
|
||||
<string>album</string>
|
||||
<key>uuid</key>
|
||||
<string>DFFKmHt3Tk+AGzZLe2Xq+g</string>
|
||||
</dict>
|
||||
<key>lastKnownItemCounts</key>
|
||||
<dict>
|
||||
<key>other</key>
|
||||
<integer>0</integer>
|
||||
<key>photos</key>
|
||||
<integer>7</integer>
|
||||
<key>videos</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>FaceProcessingInternalVersion</key>
|
||||
<integer>11</integer>
|
||||
</dict>
|
||||
</plist>
|
||||