Added {detected_text} template
This commit is contained in:
224
README.md
224
README.md
@@ -573,13 +573,13 @@ osxphotos is very flexible. If you merely want to backup your Photos library, t
|
|||||||
Usage: osxphotos export [OPTIONS] [PHOTOS_LIBRARY]... DEST
|
Usage: osxphotos export [OPTIONS] [PHOTOS_LIBRARY]... DEST
|
||||||
|
|
||||||
Export photos from the Photos database. Export path DEST is required.
|
Export photos from the Photos database. Export path DEST is required.
|
||||||
Optionally, query the Photos database using 1 or more search options; if
|
Optionally, query the Photos database using 1 or more search options; if more
|
||||||
more than one option is provided, they are treated as "AND" (e.g. search for
|
than one option is provided, they are treated as "AND" (e.g. search for photos
|
||||||
photos matching all options). If no query options are provided, all photos
|
matching all options). If no query options are provided, all photos will be
|
||||||
will be exported. By default, all versions of all photos will be exported
|
exported. By default, all versions of all photos will be exported including
|
||||||
including edited versions, live photo movies, burst photos, and associated
|
edited versions, live photo movies, burst photos, and associated raw images.
|
||||||
raw images. See --skip-edited, --skip-live, --skip-bursts, and --skip-raw
|
See --skip-edited, --skip-live, --skip-bursts, and --skip-raw options to
|
||||||
options to modify this behavior.
|
modify this behavior.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--db <Photos database path> Specify Photos database path. Path to Photos
|
--db <Photos database path> Specify Photos database path. Path to Photos
|
||||||
@@ -590,63 +590,49 @@ Options:
|
|||||||
use in the following order: 1. last opened
|
use in the following order: 1. last opened
|
||||||
library, 2. system library, 3.
|
library, 2. system library, 3.
|
||||||
~/Pictures/Photos Library.photoslibrary
|
~/Pictures/Photos Library.photoslibrary
|
||||||
|
|
||||||
-V, --verbose Print verbose output.
|
-V, --verbose Print verbose output.
|
||||||
--keyword KEYWORD Search for photos with keyword KEYWORD. If
|
--keyword KEYWORD Search for photos with keyword KEYWORD. If
|
||||||
more than one keyword, treated as "OR", e.g.
|
more than one keyword, treated as "OR", e.g.
|
||||||
find photos matching any keyword
|
find photos matching any keyword
|
||||||
|
|
||||||
--person PERSON Search for photos with person PERSON. If more
|
--person PERSON Search for photos with person PERSON. If more
|
||||||
than one person, treated as "OR", e.g. find
|
than one person, treated as "OR", e.g. find
|
||||||
photos matching any person
|
photos matching any person
|
||||||
|
|
||||||
--album ALBUM Search for photos in album ALBUM. If more than
|
--album ALBUM Search for photos in album ALBUM. If more than
|
||||||
one album, treated as "OR", e.g. find photos
|
one album, treated as "OR", e.g. find photos
|
||||||
matching any album
|
matching any album
|
||||||
|
|
||||||
--folder FOLDER Search for photos in an album in folder
|
--folder FOLDER Search for photos in an album in folder
|
||||||
FOLDER. If more than one folder, treated as
|
FOLDER. If more than one folder, treated as
|
||||||
"OR", e.g. find photos in any FOLDER. Only
|
"OR", e.g. find photos in any FOLDER. Only
|
||||||
searches top level folders (e.g. does not look
|
searches top level folders (e.g. does not look
|
||||||
at subfolders)
|
at subfolders)
|
||||||
|
|
||||||
--name FILENAME Search for photos with filename matching
|
--name FILENAME Search for photos with filename matching
|
||||||
FILENAME. If more than one --name options is
|
FILENAME. If more than one --name options is
|
||||||
specified, they are treated as "OR", e.g. find
|
specified, they are treated as "OR", e.g. find
|
||||||
photos matching any FILENAME.
|
photos matching any FILENAME.
|
||||||
|
|
||||||
--uuid UUID Search for photos with UUID(s).
|
--uuid UUID Search for photos with UUID(s).
|
||||||
--uuid-from-file FILE Search for photos with UUID(s) loaded from
|
--uuid-from-file FILE Search for photos with UUID(s) loaded from
|
||||||
FILE. Format is a single UUID per line. Lines
|
FILE. Format is a single UUID per line. Lines
|
||||||
preceded with # are ignored.
|
preceded with # are ignored.
|
||||||
|
|
||||||
--title TITLE Search for TITLE in title of photo.
|
--title TITLE Search for TITLE in title of photo.
|
||||||
--no-title Search for photos with no title.
|
--no-title Search for photos with no title.
|
||||||
--description DESC Search for DESC in description of photo.
|
--description DESC Search for DESC in description of photo.
|
||||||
--no-description Search for photos with no description.
|
--no-description Search for photos with no description.
|
||||||
--place PLACE Search for PLACE in photo's reverse
|
--place PLACE Search for PLACE in photo's reverse
|
||||||
geolocation info
|
geolocation info
|
||||||
|
|
||||||
--no-place Search for photos with no associated place
|
--no-place Search for photos with no associated place
|
||||||
name info (no reverse geolocation info)
|
name info (no reverse geolocation info)
|
||||||
|
|
||||||
--location Search for photos with associated location
|
--location Search for photos with associated location
|
||||||
info (e.g. GPS coordinates)
|
info (e.g. GPS coordinates)
|
||||||
|
|
||||||
--no-location Search for photos with no associated location
|
--no-location Search for photos with no associated location
|
||||||
info (e.g. no GPS coordinates)
|
info (e.g. no GPS coordinates)
|
||||||
|
|
||||||
--label LABEL Search for photos with image classification
|
--label LABEL Search for photos with image classification
|
||||||
label LABEL (Photos 5 only). If more than one
|
label LABEL (Photos 5 only). If more than one
|
||||||
label, treated as "OR", e.g. find photos
|
label, treated as "OR", e.g. find photos
|
||||||
matching any label
|
matching any label
|
||||||
|
|
||||||
--uti UTI Search for photos whose uniform type
|
--uti UTI Search for photos whose uniform type
|
||||||
identifier (UTI) matches UTI
|
identifier (UTI) matches UTI
|
||||||
|
|
||||||
-i, --ignore-case Case insensitive search for title,
|
-i, --ignore-case Case insensitive search for title,
|
||||||
description, place, keyword, person, or album.
|
description, place, keyword, person, or album.
|
||||||
|
|
||||||
--edited Search for photos that have been edited.
|
--edited Search for photos that have been edited.
|
||||||
--external-edit Search for photos edited in external editor.
|
--external-edit Search for photos edited in external editor.
|
||||||
--favorite Search for photos marked favorite.
|
--favorite Search for photos marked favorite.
|
||||||
@@ -655,67 +641,51 @@ Options:
|
|||||||
--not-hidden Search for photos not marked hidden.
|
--not-hidden Search for photos not marked hidden.
|
||||||
--shared Search for photos in shared iCloud album
|
--shared Search for photos in shared iCloud album
|
||||||
(Photos 5 only).
|
(Photos 5 only).
|
||||||
|
|
||||||
--not-shared Search for photos not in shared iCloud album
|
--not-shared Search for photos not in shared iCloud album
|
||||||
(Photos 5 only).
|
(Photos 5 only).
|
||||||
|
|
||||||
--burst Search for photos that were taken in a burst.
|
--burst Search for photos that were taken in a burst.
|
||||||
--not-burst Search for photos that are not part of a
|
--not-burst Search for photos that are not part of a
|
||||||
burst.
|
burst.
|
||||||
|
|
||||||
--live Search for Apple live photos
|
--live Search for Apple live photos
|
||||||
--not-live Search for photos that are not Apple live
|
--not-live Search for photos that are not Apple live
|
||||||
photos.
|
photos.
|
||||||
|
|
||||||
--portrait Search for Apple portrait mode photos.
|
--portrait Search for Apple portrait mode photos.
|
||||||
--not-portrait Search for photos that are not Apple portrait
|
--not-portrait Search for photos that are not Apple portrait
|
||||||
mode photos.
|
mode photos.
|
||||||
|
|
||||||
--screenshot Search for screenshot photos.
|
--screenshot Search for screenshot photos.
|
||||||
--not-screenshot Search for photos that are not screenshot
|
--not-screenshot Search for photos that are not screenshot
|
||||||
photos.
|
photos.
|
||||||
|
|
||||||
--slow-mo Search for slow motion videos.
|
--slow-mo Search for slow motion videos.
|
||||||
--not-slow-mo Search for photos that are not slow motion
|
--not-slow-mo Search for photos that are not slow motion
|
||||||
videos.
|
videos.
|
||||||
|
|
||||||
--time-lapse Search for time lapse videos.
|
--time-lapse Search for time lapse videos.
|
||||||
--not-time-lapse Search for photos that are not time lapse
|
--not-time-lapse Search for photos that are not time lapse
|
||||||
videos.
|
videos.
|
||||||
|
|
||||||
--hdr Search for high dynamic range (HDR) photos.
|
--hdr Search for high dynamic range (HDR) photos.
|
||||||
--not-hdr Search for photos that are not HDR photos.
|
--not-hdr Search for photos that are not HDR photos.
|
||||||
--selfie Search for selfies (photos taken with front-
|
--selfie Search for selfies (photos taken with front-
|
||||||
facing cameras).
|
facing cameras).
|
||||||
|
|
||||||
--not-selfie Search for photos that are not selfies.
|
--not-selfie Search for photos that are not selfies.
|
||||||
--panorama Search for panorama photos.
|
--panorama Search for panorama photos.
|
||||||
--not-panorama Search for photos that are not panoramas.
|
--not-panorama Search for photos that are not panoramas.
|
||||||
--has-raw Search for photos with both a jpeg and raw
|
--has-raw Search for photos with both a jpeg and raw
|
||||||
version
|
version
|
||||||
|
|
||||||
--only-movies Search only for movies (default searches both
|
--only-movies Search only for movies (default searches both
|
||||||
images and movies).
|
images and movies).
|
||||||
|
|
||||||
--only-photos Search only for photos/images (default
|
--only-photos Search only for photos/images (default
|
||||||
searches both images and movies).
|
searches both images and movies).
|
||||||
|
|
||||||
--from-date DATETIME Search by item start date, e.g.
|
--from-date DATETIME Search by item start date, e.g.
|
||||||
2000-01-12T12:00:00,
|
2000-01-12T12:00:00,
|
||||||
2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO
|
2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO
|
||||||
8601 with/without timezone).
|
8601 with/without timezone).
|
||||||
|
|
||||||
--to-date DATETIME Search by item end date, e.g.
|
--to-date DATETIME Search by item end date, e.g.
|
||||||
2000-01-12T12:00:00,
|
2000-01-12T12:00:00,
|
||||||
2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO
|
2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO
|
||||||
8601 with/without timezone).
|
8601 with/without timezone).
|
||||||
|
|
||||||
--from-time TIME Search by item start time of day, e.g. 12:00,
|
--from-time TIME Search by item start time of day, e.g. 12:00,
|
||||||
or 12:00:00.
|
or 12:00:00.
|
||||||
|
|
||||||
--to-time TIME Search by item end time of day, e.g. 12:00 or
|
--to-time TIME Search by item end time of day, e.g. 12:00 or
|
||||||
12:00:00.
|
12:00:00.
|
||||||
|
|
||||||
--has-comment Search for photos that have comments.
|
--has-comment Search for photos that have comments.
|
||||||
--no-comment Search for photos with no comments.
|
--no-comment Search for photos with no comments.
|
||||||
--has-likes Search for photos that have likes.
|
--has-likes Search for photos that have likes.
|
||||||
@@ -723,10 +693,8 @@ Options:
|
|||||||
--is-reference Search for photos that were imported as
|
--is-reference Search for photos that were imported as
|
||||||
referenced files (not copied into Photos
|
referenced files (not copied into Photos
|
||||||
library).
|
library).
|
||||||
|
|
||||||
--in-album Search for photos that are in one or more
|
--in-album Search for photos that are in one or more
|
||||||
albums.
|
albums.
|
||||||
|
|
||||||
--not-in-album Search for photos that are not in any albums.
|
--not-in-album Search for photos that are not in any albums.
|
||||||
--duplicate Search for photos with possible duplicates.
|
--duplicate Search for photos with possible duplicates.
|
||||||
osxphotos will compare signatures of photos,
|
osxphotos will compare signatures of photos,
|
||||||
@@ -736,7 +704,6 @@ Options:
|
|||||||
for-byte nor compare hashes but should find
|
for-byte nor compare hashes but should find
|
||||||
photos imported multiple times or duplicated
|
photos imported multiple times or duplicated
|
||||||
within Photos.
|
within Photos.
|
||||||
|
|
||||||
--min-size SIZE Search for photos with size >= SIZE bytes. The
|
--min-size SIZE Search for photos with size >= SIZE bytes. The
|
||||||
size evaluated is the photo's original size
|
size evaluated is the photo's original size
|
||||||
(when imported to Photos). Size may be
|
(when imported to Photos). Size may be
|
||||||
@@ -744,7 +711,6 @@ Options:
|
|||||||
units. For example, the following are all
|
units. For example, the following are all
|
||||||
valid and equivalent sizes: '1048576'
|
valid and equivalent sizes: '1048576'
|
||||||
'1.048576MB', '1 MiB'.
|
'1.048576MB', '1 MiB'.
|
||||||
|
|
||||||
--max-size SIZE Search for photos with size <= SIZE bytes. The
|
--max-size SIZE Search for photos with size <= SIZE bytes. The
|
||||||
size evaluated is the photo's original size
|
size evaluated is the photo's original size
|
||||||
(when imported to Photos). Size may be
|
(when imported to Photos). Size may be
|
||||||
@@ -752,17 +718,14 @@ Options:
|
|||||||
units. For example, the following are all
|
units. For example, the following are all
|
||||||
valid and equivalent sizes: '1048576'
|
valid and equivalent sizes: '1048576'
|
||||||
'1.048576MB', '1 MiB'.
|
'1.048576MB', '1 MiB'.
|
||||||
|
|
||||||
--regex REGEX TEMPLATE Search for photos where TEMPLATE matches
|
--regex REGEX TEMPLATE Search for photos where TEMPLATE matches
|
||||||
regular expression REGEX. For example, to find
|
regular expression REGEX. For example, to find
|
||||||
photos in an album that begins with 'Beach': '
|
photos in an album that begins with 'Beach': '
|
||||||
--regex "^Beach" "{album}"'. You may specify
|
--regex "^Beach" "{album}"'. You may specify
|
||||||
more than one regular expression match by
|
more than one regular expression match by
|
||||||
repeating '--regex' with different arguments.
|
repeating '--regex' with different arguments.
|
||||||
|
|
||||||
--selected Filter for photos that are currently selected
|
--selected Filter for photos that are currently selected
|
||||||
in Photos.
|
in Photos.
|
||||||
|
|
||||||
--query-eval CRITERIA Evaluate CRITERIA to filter photos. CRITERIA
|
--query-eval CRITERIA Evaluate CRITERIA to filter photos. CRITERIA
|
||||||
will be evaluated in context of the following
|
will be evaluated in context of the following
|
||||||
python list comprehension: `photos = [photo
|
python list comprehension: `photos = [photo
|
||||||
@@ -777,7 +740,6 @@ Options:
|
|||||||
https://rhettbull.github.io/osxphotos/ for
|
https://rhettbull.github.io/osxphotos/ for
|
||||||
additional documentation on the PhotoInfo
|
additional documentation on the PhotoInfo
|
||||||
class.
|
class.
|
||||||
|
|
||||||
--query-function filename.py::function
|
--query-function filename.py::function
|
||||||
Run function to filter photos. Use this in
|
Run function to filter photos. Use this in
|
||||||
format: --query-function filename.py::function
|
format: --query-function filename.py::function
|
||||||
@@ -794,19 +756,14 @@ Options:
|
|||||||
evaluated. See https://github.com/RhetTbull/os
|
evaluated. See https://github.com/RhetTbull/os
|
||||||
xphotos/blob/master/examples/query_function.py
|
xphotos/blob/master/examples/query_function.py
|
||||||
for example of how to use this option.
|
for example of how to use this option.
|
||||||
|
|
||||||
--missing Export only photos missing from the Photos
|
--missing Export only photos missing from the Photos
|
||||||
library; must be used with --download-missing.
|
library; must be used with --download-missing.
|
||||||
|
|
||||||
--deleted Include photos from the 'Recently Deleted'
|
--deleted Include photos from the 'Recently Deleted'
|
||||||
folder.
|
folder.
|
||||||
|
|
||||||
--deleted-only Include only photos from the 'Recently
|
--deleted-only Include only photos from the 'Recently
|
||||||
Deleted' folder.
|
Deleted' folder.
|
||||||
|
|
||||||
--update Only export new or updated files. See notes
|
--update Only export new or updated files. See notes
|
||||||
below on export and --update.
|
below on export and --update.
|
||||||
|
|
||||||
--ignore-signature When used with '--update', ignores file
|
--ignore-signature When used with '--update', ignores file
|
||||||
signature when updating files. This is useful
|
signature when updating files. This is useful
|
||||||
if you have processed or edited exported
|
if you have processed or edited exported
|
||||||
@@ -825,15 +782,12 @@ Options:
|
|||||||
not; 3) if a sidecar does not exist for the
|
not; 3) if a sidecar does not exist for the
|
||||||
photo, a sidecar will be written whether or
|
photo, a sidecar will be written whether or
|
||||||
not the photo file was written or updated.
|
not the photo file was written or updated.
|
||||||
|
|
||||||
--only-new If used with --update, ignores any previously
|
--only-new If used with --update, ignores any previously
|
||||||
exported files, even if missing from the
|
exported files, even if missing from the
|
||||||
export folder and only exports new files that
|
export folder and only exports new files that
|
||||||
haven't previously been exported.
|
haven't previously been exported.
|
||||||
|
|
||||||
--dry-run Dry run (test) the export but don't actually
|
--dry-run Dry run (test) the export but don't actually
|
||||||
export any files; most useful with --verbose.
|
export any files; most useful with --verbose.
|
||||||
|
|
||||||
--export-as-hardlink Hardlink files instead of copying them. Cannot
|
--export-as-hardlink Hardlink files instead of copying them. Cannot
|
||||||
be used with --exiftool which creates copies
|
be used with --exiftool which creates copies
|
||||||
of the files with embedded EXIF data. Note: on
|
of the files with embedded EXIF data. Note: on
|
||||||
@@ -841,49 +795,38 @@ Options:
|
|||||||
giving many of the same advantages as
|
giving many of the same advantages as
|
||||||
hardlinks without having to use --export-as-
|
hardlinks without having to use --export-as-
|
||||||
hardlink.
|
hardlink.
|
||||||
|
|
||||||
--touch-file Sets the file's modification time to match
|
--touch-file Sets the file's modification time to match
|
||||||
photo date.
|
photo date.
|
||||||
|
|
||||||
--overwrite Overwrite existing files. Default behavior is
|
--overwrite Overwrite existing files. Default behavior is
|
||||||
to add (1), (2), etc to filename if file
|
to add (1), (2), etc to filename if file
|
||||||
already exists. Use this with caution as it
|
already exists. Use this with caution as it
|
||||||
may create name collisions on export. (e.g. if
|
may create name collisions on export. (e.g. if
|
||||||
two files happen to have the same name)
|
two files happen to have the same name)
|
||||||
|
|
||||||
--retry RETRY Automatically retry export up to RETRY times
|
--retry RETRY Automatically retry export up to RETRY times
|
||||||
if an error occurs during export. This may be
|
if an error occurs during export. This may be
|
||||||
useful with network drives that experience
|
useful with network drives that experience
|
||||||
intermittent errors.
|
intermittent errors.
|
||||||
|
|
||||||
--export-by-date Automatically create output folders to
|
--export-by-date Automatically create output folders to
|
||||||
organize photos by date created (e.g.
|
organize photos by date created (e.g.
|
||||||
DEST/2019/12/20/photoname.jpg).
|
DEST/2019/12/20/photoname.jpg).
|
||||||
|
|
||||||
--skip-edited Do not export edited version of photo if an
|
--skip-edited Do not export edited version of photo if an
|
||||||
edited version exists.
|
edited version exists.
|
||||||
|
|
||||||
--skip-original-if-edited Do not export original if there is an edited
|
--skip-original-if-edited Do not export original if there is an edited
|
||||||
version (exports only the edited version).
|
version (exports only the edited version).
|
||||||
|
|
||||||
--skip-bursts Do not export all associated burst images in
|
--skip-bursts Do not export all associated burst images in
|
||||||
the library if a photo is a burst photo.
|
the library if a photo is a burst photo.
|
||||||
|
|
||||||
--skip-live Do not export the associated live video
|
--skip-live Do not export the associated live video
|
||||||
component of a live photo.
|
component of a live photo.
|
||||||
|
|
||||||
--skip-raw Do not export associated RAW image of a
|
--skip-raw Do not export associated RAW image of a
|
||||||
RAW+JPEG pair. Note: this does not skip RAW
|
RAW+JPEG pair. Note: this does not skip RAW
|
||||||
photos if the RAW photo does not have an
|
photos if the RAW photo does not have an
|
||||||
associated JPEG image (e.g. the RAW file was
|
associated JPEG image (e.g. the RAW file was
|
||||||
imported to Photos without a JPEG preview).
|
imported to Photos without a JPEG preview).
|
||||||
|
|
||||||
--current-name Use photo's current filename instead of
|
--current-name Use photo's current filename instead of
|
||||||
original filename for export. Note: Starting
|
original filename for export. Note: Starting
|
||||||
with Photos 5, all photos are renamed upon
|
with Photos 5, all photos are renamed upon
|
||||||
import. By default, photos are exported with
|
import. By default, photos are exported with
|
||||||
the the original name they had before import.
|
the the original name they had before import.
|
||||||
|
|
||||||
--convert-to-jpeg Convert all non-JPEG images (e.g. RAW, HEIC,
|
--convert-to-jpeg Convert all non-JPEG images (e.g. RAW, HEIC,
|
||||||
PNG, etc) to JPEG upon export. Note: does not
|
PNG, etc) to JPEG upon export. Note: does not
|
||||||
convert the RAW component of a RAW+JPEG pair
|
convert the RAW component of a RAW+JPEG pair
|
||||||
@@ -893,24 +836,20 @@ Options:
|
|||||||
also --jpeg-quality and --jpeg-ext. Only works
|
also --jpeg-quality and --jpeg-ext. Only works
|
||||||
if your Mac has a GPU (thus may not work on
|
if your Mac has a GPU (thus may not work on
|
||||||
virtual machines).
|
virtual machines).
|
||||||
|
|
||||||
--jpeg-quality FLOAT RANGE Value in range 0.0 to 1.0 to use with
|
--jpeg-quality FLOAT RANGE Value in range 0.0 to 1.0 to use with
|
||||||
--convert-to-jpeg. A value of 1.0 specifies
|
--convert-to-jpeg. A value of 1.0 specifies
|
||||||
best quality, a value of 0.0 specifies maximum
|
best quality, a value of 0.0 specifies maximum
|
||||||
compression. Defaults to 1.0
|
compression. Defaults to 1.0 [0.0<=x<=1.0]
|
||||||
|
|
||||||
--preview Export preview image generated by Photos. This
|
--preview Export preview image generated by Photos. This
|
||||||
is a lower-resolution image used by Photos to
|
is a lower-resolution image used by Photos to
|
||||||
quickly preview the image. See also --preview-
|
quickly preview the image. See also --preview-
|
||||||
suffix and --preview-if-missing.
|
suffix and --preview-if-missing.
|
||||||
|
|
||||||
--preview-if-missing Export preview image generated by Photos if
|
--preview-if-missing Export preview image generated by Photos if
|
||||||
the actual photo file is missing from the
|
the actual photo file is missing from the
|
||||||
library. This may be helpful if photos were
|
library. This may be helpful if photos were
|
||||||
not copied to the Photos library and the
|
not copied to the Photos library and the
|
||||||
original photo is missing. See also --preview-
|
original photo is missing. See also --preview-
|
||||||
suffix and --preview.
|
suffix and --preview.
|
||||||
|
|
||||||
--preview-suffix SUFFIX Optional suffix template for naming preview
|
--preview-suffix SUFFIX Optional suffix template for naming preview
|
||||||
photos. Default name for preview photos is in
|
photos. Default name for preview photos is in
|
||||||
form 'photoname_preview.ext'. For example,
|
form 'photoname_preview.ext'. For example,
|
||||||
@@ -920,7 +859,6 @@ Options:
|
|||||||
templates (see Templating System) are not
|
templates (see Templating System) are not
|
||||||
permitted with --preview-suffix. See also
|
permitted with --preview-suffix. See also
|
||||||
--preview and --preview-if-missing.
|
--preview and --preview-if-missing.
|
||||||
|
|
||||||
--download-missing Attempt to download missing photos from
|
--download-missing Attempt to download missing photos from
|
||||||
iCloud. The current implementation uses
|
iCloud. The current implementation uses
|
||||||
Applescript to interact with Photos to export
|
Applescript to interact with Photos to export
|
||||||
@@ -933,7 +871,6 @@ Options:
|
|||||||
export all burst images; only the primary
|
export all burst images; only the primary
|
||||||
photo will be exported--associated burst
|
photo will be exported--associated burst
|
||||||
images will be skipped.
|
images will be skipped.
|
||||||
|
|
||||||
--sidecar FORMAT Create sidecar for each photo exported; valid
|
--sidecar FORMAT Create sidecar for each photo exported; valid
|
||||||
FORMAT values: xmp, json, exiftool; --sidecar
|
FORMAT values: xmp, json, exiftool; --sidecar
|
||||||
xmp: create XMP sidecar used by Digikam, Adobe
|
xmp: create XMP sidecar used by Digikam, Adobe
|
||||||
@@ -960,7 +897,6 @@ Options:
|
|||||||
tags exported in the JSON and exiftool
|
tags exported in the JSON and exiftool
|
||||||
sidecar, see '--exiftool'. See also '--ignore-
|
sidecar, see '--exiftool'. See also '--ignore-
|
||||||
signature'.
|
signature'.
|
||||||
|
|
||||||
--sidecar-drop-ext Drop the photo's extension when naming sidecar
|
--sidecar-drop-ext Drop the photo's extension when naming sidecar
|
||||||
files. By default, sidecar files are named in
|
files. By default, sidecar files are named in
|
||||||
format 'photo_filename.photo_ext.sidecar_ext',
|
format 'photo_filename.photo_ext.sidecar_ext',
|
||||||
@@ -972,7 +908,6 @@ Options:
|
|||||||
of different types but the same name in the
|
of different types but the same name in the
|
||||||
output directory, e.g. 'IMG_1234.JPG' and
|
output directory, e.g. 'IMG_1234.JPG' and
|
||||||
'IMG_1234.MOV'.
|
'IMG_1234.MOV'.
|
||||||
|
|
||||||
--exiftool Use exiftool to write metadata directly to
|
--exiftool Use exiftool to write metadata directly to
|
||||||
exported photos. To use this option, exiftool
|
exported photos. To use this option, exiftool
|
||||||
must be installed and in the path. exiftool
|
must be installed and in the path. exiftool
|
||||||
@@ -994,10 +929,8 @@ Options:
|
|||||||
QuickTime:ModifyDate (see also --ignore-date-
|
QuickTime:ModifyDate (see also --ignore-date-
|
||||||
modified); QuickTime:GPSCoordinates;
|
modified); QuickTime:GPSCoordinates;
|
||||||
UserData:GPSCoordinates.
|
UserData:GPSCoordinates.
|
||||||
|
|
||||||
--exiftool-path EXIFTOOL_PATH Optionally specify path to exiftool; if not
|
--exiftool-path EXIFTOOL_PATH Optionally specify path to exiftool; if not
|
||||||
provided, will look for exiftool in $PATH.
|
provided, will look for exiftool in $PATH.
|
||||||
|
|
||||||
--exiftool-option OPTION Optional flag/option to pass to exiftool when
|
--exiftool-option OPTION Optional flag/option to pass to exiftool when
|
||||||
using --exiftool. For example, --exiftool-
|
using --exiftool. For example, --exiftool-
|
||||||
option '-m' to ignore minor warnings. Specify
|
option '-m' to ignore minor warnings. Specify
|
||||||
@@ -1007,27 +940,21 @@ Options:
|
|||||||
full list of options. More than one option may
|
full list of options. More than one option may
|
||||||
be specified by repeating the option, e.g.
|
be specified by repeating the option, e.g.
|
||||||
--exiftool-option '-m' --exiftool-option '-F'.
|
--exiftool-option '-m' --exiftool-option '-F'.
|
||||||
|
|
||||||
--exiftool-merge-keywords Merge any keywords found in the original file
|
--exiftool-merge-keywords Merge any keywords found in the original file
|
||||||
with keywords used for '--exiftool' and '--
|
with keywords used for '--exiftool' and '--
|
||||||
sidecar'.
|
sidecar'.
|
||||||
|
|
||||||
--exiftool-merge-persons Merge any persons found in the original file
|
--exiftool-merge-persons Merge any persons found in the original file
|
||||||
with persons used for '--exiftool' and '--
|
with persons used for '--exiftool' and '--
|
||||||
sidecar'.
|
sidecar'.
|
||||||
|
|
||||||
--ignore-date-modified If used with --exiftool or --sidecar, will
|
--ignore-date-modified If used with --exiftool or --sidecar, will
|
||||||
ignore the photo modification date and set
|
ignore the photo modification date and set
|
||||||
EXIF:ModifyDate to EXIF:DateTimeOriginal; this
|
EXIF:ModifyDate to EXIF:DateTimeOriginal; this
|
||||||
is consistent with how Photos handles the
|
is consistent with how Photos handles the
|
||||||
EXIF:ModifyDate tag.
|
EXIF:ModifyDate tag.
|
||||||
|
|
||||||
--person-keyword Use person in image as keyword/tag when
|
--person-keyword Use person in image as keyword/tag when
|
||||||
exporting metadata.
|
exporting metadata.
|
||||||
|
|
||||||
--album-keyword Use album name as keyword/tag when exporting
|
--album-keyword Use album name as keyword/tag when exporting
|
||||||
metadata.
|
metadata.
|
||||||
|
|
||||||
--keyword-template TEMPLATE For use with --exiftool, --sidecar; specify a
|
--keyword-template TEMPLATE For use with --exiftool, --sidecar; specify a
|
||||||
template string to use as keyword in the form
|
template string to use as keyword in the form
|
||||||
'{name,DEFAULT}' This is the same format as
|
'{name,DEFAULT}' This is the same format as
|
||||||
@@ -1040,7 +967,6 @@ Options:
|
|||||||
"{folder_album}" --keyword-template
|
"{folder_album}" --keyword-template
|
||||||
"{created.year}". See '--replace-keywords' and
|
"{created.year}". See '--replace-keywords' and
|
||||||
Templating System below.
|
Templating System below.
|
||||||
|
|
||||||
--replace-keywords Replace keywords with any values specified
|
--replace-keywords Replace keywords with any values specified
|
||||||
with --keyword-template. By default,
|
with --keyword-template. By default,
|
||||||
--keyword-template will add keywords to any
|
--keyword-template will add keywords to any
|
||||||
@@ -1049,7 +975,6 @@ Options:
|
|||||||
from --keyword-template will replace any
|
from --keyword-template will replace any
|
||||||
existing keywords instead of adding additional
|
existing keywords instead of adding additional
|
||||||
keywords.
|
keywords.
|
||||||
|
|
||||||
--description-template TEMPLATE
|
--description-template TEMPLATE
|
||||||
For use with --exiftool, --sidecar; specify a
|
For use with --exiftool, --sidecar; specify a
|
||||||
template string to use as description in the
|
template string to use as description in the
|
||||||
@@ -1060,7 +985,6 @@ Options:
|
|||||||
--description-template "{descr} exported with
|
--description-template "{descr} exported with
|
||||||
osxphotos on {today.date}" See Templating
|
osxphotos on {today.date}" See Templating
|
||||||
System below.
|
System below.
|
||||||
|
|
||||||
--finder-tag-template TEMPLATE Set MacOS Finder tags to TEMPLATE. These tags
|
--finder-tag-template TEMPLATE Set MacOS Finder tags to TEMPLATE. These tags
|
||||||
can be searched in the Finder or Spotlight
|
can be searched in the Finder or Spotlight
|
||||||
with 'tag:tagname' format. For example, '--
|
with 'tag:tagname' format. For example, '--
|
||||||
@@ -1069,13 +993,11 @@ Options:
|
|||||||
TEMPLATE values by using '--finder-tag-
|
TEMPLATE values by using '--finder-tag-
|
||||||
template' multiple times. See also '--finder-
|
template' multiple times. See also '--finder-
|
||||||
tag-keywords and Extended Attributes below.'.
|
tag-keywords and Extended Attributes below.'.
|
||||||
|
|
||||||
--finder-tag-keywords Set MacOS Finder tags to keywords; any
|
--finder-tag-keywords Set MacOS Finder tags to keywords; any
|
||||||
keywords specified via '--keyword-template', '
|
keywords specified via '--keyword-template', '
|
||||||
--person-keyword', etc. will also be used as
|
--person-keyword', etc. will also be used as
|
||||||
Finder tags. See also '--finder-tag-template
|
Finder tags. See also '--finder-tag-template
|
||||||
and Extended Attributes below.'.
|
and Extended Attributes below.'.
|
||||||
|
|
||||||
--xattr-template ATTRIBUTE TEMPLATE
|
--xattr-template ATTRIBUTE TEMPLATE
|
||||||
Set extended attribute ATTRIBUTE to TEMPLATE
|
Set extended attribute ATTRIBUTE to TEMPLATE
|
||||||
value. Valid attributes are: 'authors',
|
value. Valid attributes are: 'authors',
|
||||||
@@ -1088,19 +1010,16 @@ Options:
|
|||||||
findercomment "{title}; {descr}" See Extended
|
findercomment "{title}; {descr}" See Extended
|
||||||
Attributes below for additional details on
|
Attributes below for additional details on
|
||||||
this option.
|
this option.
|
||||||
|
|
||||||
--directory DIRECTORY Optional template for specifying name of
|
--directory DIRECTORY Optional template for specifying name of
|
||||||
output directory in the form '{name,DEFAULT}'.
|
output directory in the form '{name,DEFAULT}'.
|
||||||
See below for additional details on templating
|
See below for additional details on templating
|
||||||
system.
|
system.
|
||||||
|
|
||||||
--filename FILENAME Optional template for specifying name of
|
--filename FILENAME Optional template for specifying name of
|
||||||
output file in the form '{name,DEFAULT}'. File
|
output file in the form '{name,DEFAULT}'. File
|
||||||
extension will be added automatically--do not
|
extension will be added automatically--do not
|
||||||
include an extension in the FILENAME template.
|
include an extension in the FILENAME template.
|
||||||
See below for additional details on templating
|
See below for additional details on templating
|
||||||
system.
|
system.
|
||||||
|
|
||||||
--jpeg-ext EXTENSION Specify file extension for JPEG files. Photos
|
--jpeg-ext EXTENSION Specify file extension for JPEG files. Photos
|
||||||
uses .jpeg for edited images but many images
|
uses .jpeg for edited images but many images
|
||||||
are imported with .jpg or .JPG which can
|
are imported with .jpg or .JPG which can
|
||||||
@@ -1110,14 +1029,12 @@ Options:
|
|||||||
exported JPEG images. Valid values are jpeg,
|
exported JPEG images. Valid values are jpeg,
|
||||||
jpg, JPEG, JPG; e.g. '--jpeg-ext jpg' to use
|
jpg, JPEG, JPG; e.g. '--jpeg-ext jpg' to use
|
||||||
'.jpg' for all JPEGs.
|
'.jpg' for all JPEGs.
|
||||||
|
|
||||||
--strip Optionally strip leading and trailing
|
--strip Optionally strip leading and trailing
|
||||||
whitespace from any rendered templates. For
|
whitespace from any rendered templates. For
|
||||||
example, if --filename template is "{title,}
|
example, if --filename template is "{title,}
|
||||||
{original_name}" and image has no title,
|
{original_name}" and image has no title,
|
||||||
resulting file would have a leading space but
|
resulting file would have a leading space but
|
||||||
if used with --strip, this will be removed.
|
if used with --strip, this will be removed.
|
||||||
|
|
||||||
--edited-suffix SUFFIX Optional suffix template for naming edited
|
--edited-suffix SUFFIX Optional suffix template for naming edited
|
||||||
photos. Default name for edited photos is in
|
photos. Default name for edited photos is in
|
||||||
form 'photoname_edited.ext'. For example, with
|
form 'photoname_edited.ext'. For example, with
|
||||||
@@ -1127,7 +1044,6 @@ Options:
|
|||||||
suffix is '_edited'. Multi-value templates
|
suffix is '_edited'. Multi-value templates
|
||||||
(see Templating System) are not permitted with
|
(see Templating System) are not permitted with
|
||||||
--edited-suffix.
|
--edited-suffix.
|
||||||
|
|
||||||
--original-suffix SUFFIX Optional suffix template for naming original
|
--original-suffix SUFFIX Optional suffix template for naming original
|
||||||
photos. Default name for original photos is
|
photos. Default name for original photos is
|
||||||
in form 'filename.ext'. For example, with '--
|
in form 'filename.ext'. For example, with '--
|
||||||
@@ -1136,11 +1052,9 @@ Options:
|
|||||||
default suffix is '' (no suffix). Multi-value
|
default suffix is '' (no suffix). Multi-value
|
||||||
templates (see Templating System) are not
|
templates (see Templating System) are not
|
||||||
permitted with --original-suffix.
|
permitted with --original-suffix.
|
||||||
|
|
||||||
--use-photos-export Force the use of AppleScript or PhotoKit to
|
--use-photos-export Force the use of AppleScript or PhotoKit to
|
||||||
export even if not missing (see also '--
|
export even if not missing (see also '--
|
||||||
download-missing' and '--use-photokit').
|
download-missing' and '--use-photokit').
|
||||||
|
|
||||||
--use-photokit Use with '--download-missing' or '--use-
|
--use-photokit Use with '--download-missing' or '--use-
|
||||||
photos-export' to use direct Photos interface
|
photos-export' to use direct Photos interface
|
||||||
instead of AppleScript to export. Highly
|
instead of AppleScript to export. Highly
|
||||||
@@ -1148,11 +1062,9 @@ Options:
|
|||||||
iTerm2 (use with Terminal.app). This is faster
|
iTerm2 (use with Terminal.app). This is faster
|
||||||
and more reliable than the default AppleScript
|
and more reliable than the default AppleScript
|
||||||
interface.
|
interface.
|
||||||
|
|
||||||
--report <path to export report>
|
--report <path to export report>
|
||||||
Write a CSV formatted report of all files that
|
Write a CSV formatted report of all files that
|
||||||
were exported.
|
were exported.
|
||||||
|
|
||||||
--cleanup Cleanup export directory by deleting any files
|
--cleanup Cleanup export directory by deleting any files
|
||||||
which were not included in this export set.
|
which were not included in this export set.
|
||||||
For example, photos which had previously been
|
For example, photos which had previously been
|
||||||
@@ -1164,7 +1076,6 @@ Options:
|
|||||||
you intend before using --cleanup. Use --dry-
|
you intend before using --cleanup. Use --dry-
|
||||||
run with --cleanup first if you're not
|
run with --cleanup first if you're not
|
||||||
certain.
|
certain.
|
||||||
|
|
||||||
--add-exported-to-album ALBUM Add all exported photos to album ALBUM in
|
--add-exported-to-album ALBUM Add all exported photos to album ALBUM in
|
||||||
Photos. Album ALBUM will be created if it
|
Photos. Album ALBUM will be created if it
|
||||||
doesn't exist. All exported photos will be
|
doesn't exist. All exported photos will be
|
||||||
@@ -1174,7 +1085,6 @@ Options:
|
|||||||
feature is currently experimental. I don't
|
feature is currently experimental. I don't
|
||||||
know how well it will work on large export
|
know how well it will work on large export
|
||||||
sets.
|
sets.
|
||||||
|
|
||||||
--add-skipped-to-album ALBUM Add all skipped photos to album ALBUM in
|
--add-skipped-to-album ALBUM Add all skipped photos to album ALBUM in
|
||||||
Photos. Album ALBUM will be created if it
|
Photos. Album ALBUM will be created if it
|
||||||
doesn't exist. All skipped photos will be
|
doesn't exist. All skipped photos will be
|
||||||
@@ -1184,7 +1094,6 @@ Options:
|
|||||||
feature is currently experimental. I don't
|
feature is currently experimental. I don't
|
||||||
know how well it will work on large export
|
know how well it will work on large export
|
||||||
sets.
|
sets.
|
||||||
|
|
||||||
--add-missing-to-album ALBUM Add all missing photos to album ALBUM in
|
--add-missing-to-album ALBUM Add all missing photos to album ALBUM in
|
||||||
Photos. Album ALBUM will be created if it
|
Photos. Album ALBUM will be created if it
|
||||||
doesn't exist. All missing photos will be
|
doesn't exist. All missing photos will be
|
||||||
@@ -1194,7 +1103,6 @@ Options:
|
|||||||
feature is currently experimental. I don't
|
feature is currently experimental. I don't
|
||||||
know how well it will work on large export
|
know how well it will work on large export
|
||||||
sets.
|
sets.
|
||||||
|
|
||||||
--post-command CATEGORY COMMAND
|
--post-command CATEGORY COMMAND
|
||||||
Run COMMAND on exported files of category
|
Run COMMAND on exported files of category
|
||||||
CATEGORY. CATEGORY can be one of: exported,
|
CATEGORY. CATEGORY can be one of: exported,
|
||||||
@@ -1213,7 +1121,6 @@ Options:
|
|||||||
command by repeating the '--post-command'
|
command by repeating the '--post-command'
|
||||||
option with different arguments. See Post
|
option with different arguments. See Post
|
||||||
Command below.
|
Command below.
|
||||||
|
|
||||||
--post-function filename.py::function
|
--post-function filename.py::function
|
||||||
Run function on exported files. Use this in
|
Run function on exported files. Use this in
|
||||||
format: --post-function filename.py::function
|
format: --post-function filename.py::function
|
||||||
@@ -1226,7 +1133,6 @@ Options:
|
|||||||
You can run more than one function by
|
You can run more than one function by
|
||||||
repeating the '--post-function' option with
|
repeating the '--post-function' option with
|
||||||
different arguments. See Post Function below.
|
different arguments. See Post Function below.
|
||||||
|
|
||||||
--exportdb EXPORTDB_FILE Specify alternate name for database file which
|
--exportdb EXPORTDB_FILE Specify alternate name for database file which
|
||||||
stores state information for export and
|
stores state information for export and
|
||||||
--update. If --exportdb is not specified,
|
--update. If --exportdb is not specified,
|
||||||
@@ -1235,7 +1141,6 @@ Options:
|
|||||||
directory. Must be specified as filename
|
directory. Must be specified as filename
|
||||||
only, not a path, as export database will be
|
only, not a path, as export database will be
|
||||||
saved in export directory.
|
saved in export directory.
|
||||||
|
|
||||||
--load-config <config file path>
|
--load-config <config file path>
|
||||||
Load options from file as written with --save-
|
Load options from file as written with --save-
|
||||||
config. This allows you to save a complex
|
config. This allows you to save a complex
|
||||||
@@ -1247,11 +1152,9 @@ Options:
|
|||||||
line options are used in conjunction with
|
line options are used in conjunction with
|
||||||
--load-config, they will override the
|
--load-config, they will override the
|
||||||
corresponding values in the config file.
|
corresponding values in the config file.
|
||||||
|
|
||||||
--save-config <config file path>
|
--save-config <config file path>
|
||||||
Save options to file for use with --load-
|
Save options to file for use with --load-
|
||||||
config. File format is TOML.
|
config. File format is TOML.
|
||||||
|
|
||||||
--help Show this message and exit.
|
--help Show this message and exit.
|
||||||
|
|
||||||
** Export **
|
** Export **
|
||||||
@@ -1306,59 +1209,45 @@ The following attributes may be used with '--xattr-template':
|
|||||||
|
|
||||||
authors The author, or authors, of the contents of the file. A list of
|
authors The author, or authors, of the contents of the file. A list of
|
||||||
strings. (com.apple.metadata:kMDItemAuthors)
|
strings. (com.apple.metadata:kMDItemAuthors)
|
||||||
|
|
||||||
comment A comment related to the file. This differs from the Finder
|
comment A comment related to the file. This differs from the Finder
|
||||||
comment, kMDItemFinderComment. A string.
|
comment, kMDItemFinderComment. A string.
|
||||||
(com.apple.metadata:kMDItemComment)
|
(com.apple.metadata:kMDItemComment)
|
||||||
|
|
||||||
copyright The copyright owner of the file contents. A string.
|
copyright The copyright owner of the file contents. A string.
|
||||||
(com.apple.metadata:kMDItemCopyright)
|
(com.apple.metadata:kMDItemCopyright)
|
||||||
|
|
||||||
creator Application used to create the document content (for example
|
creator Application used to create the document content (for example
|
||||||
“Word”, “Pages”, and so on). A string.
|
“Word”, “Pages”, and so on). A string.
|
||||||
(com.apple.metadata:kMDItemCreator)
|
(com.apple.metadata:kMDItemCreator)
|
||||||
|
|
||||||
description A description of the content of the resource. The description
|
description A description of the content of the resource. The description
|
||||||
may include an abstract, table of contents, reference to a
|
may include an abstract, table of contents, reference to a
|
||||||
graphical representation of content or a free-text account of
|
graphical representation of content or a free-text account of
|
||||||
the content. A string. (com.apple.metadata:kMDItemDescription)
|
the content. A string. (com.apple.metadata:kMDItemDescription)
|
||||||
|
|
||||||
findercomment Finder comments for this file. A string.
|
findercomment Finder comments for this file. A string.
|
||||||
(com.apple.metadata:kMDItemFinderComment)
|
(com.apple.metadata:kMDItemFinderComment)
|
||||||
|
|
||||||
headline A publishable entry providing a synopsis of the contents of the
|
headline A publishable entry providing a synopsis of the contents of the
|
||||||
file. A string. (com.apple.metadata:kMDItemHeadline)
|
file. A string. (com.apple.metadata:kMDItemHeadline)
|
||||||
|
|
||||||
keywords Keywords associated with this file. For example, “Birthday”,
|
keywords Keywords associated with this file. For example, “Birthday”,
|
||||||
“Important”, etc. This differs from Finder tags
|
“Important”, etc. This differs from Finder tags
|
||||||
(_kMDItemUserTags) which are keywords/tags shown in the Finder
|
(_kMDItemUserTags) which are keywords/tags shown in the Finder
|
||||||
and searchable in Spotlight using "tag:tag_name". A list of
|
and searchable in Spotlight using "tag:tag_name". A list of
|
||||||
strings. (com.apple.metadata:kMDItemKeywords)
|
strings. (com.apple.metadata:kMDItemKeywords)
|
||||||
|
|
||||||
participants The list of people who are visible in an image or movie or
|
participants The list of people who are visible in an image or movie or
|
||||||
written about in a document. A list of strings.
|
written about in a document. A list of strings.
|
||||||
(com.apple.metadata:kMDItemParticipants)
|
(com.apple.metadata:kMDItemParticipants)
|
||||||
|
|
||||||
projects The list of projects that this file is part of. For example, if
|
projects The list of projects that this file is part of. For example, if
|
||||||
you were working on a movie all of the files could be marked as
|
you were working on a movie all of the files could be marked as
|
||||||
belonging to the project “My Movie”. A list of strings.
|
belonging to the project “My Movie”. A list of strings.
|
||||||
(com.apple.metadata:kMDItemProjects)
|
(com.apple.metadata:kMDItemProjects)
|
||||||
|
|
||||||
rating User rating of this item. For example, the stars rating of an
|
rating User rating of this item. For example, the stars rating of an
|
||||||
iTunes track. An integer.
|
iTunes track. An integer.
|
||||||
(com.apple.metadata:kMDItemStarRating)
|
(com.apple.metadata:kMDItemStarRating)
|
||||||
|
|
||||||
subject Subject of the this item. A string.
|
subject Subject of the this item. A string.
|
||||||
(com.apple.metadata:kMDItemSubject)
|
(com.apple.metadata:kMDItemSubject)
|
||||||
|
|
||||||
title The title of the file. For example, this could be the title of
|
title The title of the file. For example, this could be the title of
|
||||||
a document, the name of a song, or the subject of an email
|
a document, the name of a song, or the subject of an email
|
||||||
message. A string. (com.apple.metadata:kMDItemTitle)
|
message. A string. (com.apple.metadata:kMDItemTitle)
|
||||||
|
|
||||||
version The version number of this file. A string.
|
version The version number of this file. A string.
|
||||||
(com.apple.metadata:kMDItemVersion)
|
(com.apple.metadata:kMDItemVersion)
|
||||||
|
|
||||||
|
|
||||||
For additional information on extended attributes see: https://developer.apple.c
|
For additional information on extended attributes see: https://developer.apple.c
|
||||||
om/documentation/coreservices/file_metadata/mditem/common_metadata_attribute_key
|
om/documentation/coreservices/file_metadata/mditem/common_metadata_attribute_key
|
||||||
s
|
s
|
||||||
@@ -1584,7 +1473,6 @@ Substitution Description
|
|||||||
{name} Current filename of the photo
|
{name} Current filename of the photo
|
||||||
{original_name} Photo's original filename when imported to
|
{original_name} Photo's original filename when imported to
|
||||||
Photos
|
Photos
|
||||||
|
|
||||||
{title} Title of the photo
|
{title} Title of the photo
|
||||||
{descr} Description of the photo
|
{descr} Description of the photo
|
||||||
{media_type} Special media type resolved in this
|
{media_type} Special media type resolved in this
|
||||||
@@ -1594,48 +1482,35 @@ Substitution Description
|
|||||||
'video' if no special type. Customize one or
|
'video' if no special type. Customize one or
|
||||||
more media types using format: '{media_type,vi
|
more media types using format: '{media_type,vi
|
||||||
deo=vidéo;time_lapse=vidéo_accélérée}'
|
deo=vidéo;time_lapse=vidéo_accélérée}'
|
||||||
|
|
||||||
{photo_or_video} 'photo' or 'video' depending on what type the
|
{photo_or_video} 'photo' or 'video' depending on what type the
|
||||||
image is. To customize, use default value as
|
image is. To customize, use default value as
|
||||||
in '{photo_or_video,photo=fotos;video=videos}'
|
in '{photo_or_video,photo=fotos;video=videos}'
|
||||||
|
|
||||||
{hdr} Photo is HDR?; True/False value, use in format
|
{hdr} Photo is HDR?; True/False value, use in format
|
||||||
'{hdr?VALUE_IF_TRUE,VALUE_IF_FALSE}'
|
'{hdr?VALUE_IF_TRUE,VALUE_IF_FALSE}'
|
||||||
|
|
||||||
{edited} True if photo has been edited (has
|
{edited} True if photo has been edited (has
|
||||||
adjustments), otherwise False; use in format
|
adjustments), otherwise False; use in format
|
||||||
'{edited?VALUE_IF_TRUE,VALUE_IF_FALSE}'
|
'{edited?VALUE_IF_TRUE,VALUE_IF_FALSE}'
|
||||||
|
|
||||||
{edited_version} True if template is being rendered for the
|
{edited_version} True if template is being rendered for the
|
||||||
edited version of a photo, otherwise False.
|
edited version of a photo, otherwise False.
|
||||||
|
|
||||||
{favorite} Photo has been marked as favorite?; True/False
|
{favorite} Photo has been marked as favorite?; True/False
|
||||||
value, use in format
|
value, use in format
|
||||||
'{favorite?VALUE_IF_TRUE,VALUE_IF_FALSE}'
|
'{favorite?VALUE_IF_TRUE,VALUE_IF_FALSE}'
|
||||||
|
|
||||||
{created.date} Photo's creation date in ISO format, e.g.
|
{created.date} Photo's creation date in ISO format, e.g.
|
||||||
'2020-03-22'
|
'2020-03-22'
|
||||||
|
|
||||||
{created.year} 4-digit year of photo creation time
|
{created.year} 4-digit year of photo creation time
|
||||||
{created.yy} 2-digit year of photo creation time
|
{created.yy} 2-digit year of photo creation time
|
||||||
{created.mm} 2-digit month of the photo creation time (zero
|
{created.mm} 2-digit month of the photo creation time (zero
|
||||||
padded)
|
padded)
|
||||||
|
|
||||||
{created.month} Month name in user's locale of the photo
|
{created.month} Month name in user's locale of the photo
|
||||||
creation time
|
creation time
|
||||||
|
|
||||||
{created.mon} Month abbreviation in the user's locale of the
|
{created.mon} Month abbreviation in the user's locale of the
|
||||||
photo creation time
|
photo creation time
|
||||||
|
|
||||||
{created.dd} 2-digit day of the month (zero padded) of
|
{created.dd} 2-digit day of the month (zero padded) of
|
||||||
photo creation time
|
photo creation time
|
||||||
|
|
||||||
{created.dow} Day of week in user's locale of the photo
|
{created.dow} Day of week in user's locale of the photo
|
||||||
creation time
|
creation time
|
||||||
|
|
||||||
{created.doy} 3-digit day of year (e.g Julian day) of photo
|
{created.doy} 3-digit day of year (e.g Julian day) of photo
|
||||||
creation time, starting from 1 (zero padded)
|
creation time, starting from 1 (zero padded)
|
||||||
|
|
||||||
{created.hour} 2-digit hour of the photo creation time
|
{created.hour} 2-digit hour of the photo creation time
|
||||||
{created.min} 2-digit minute of the photo creation time
|
{created.min} 2-digit minute of the photo creation time
|
||||||
{created.sec} 2-digit second of the photo creation time
|
{created.sec} 2-digit second of the photo creation time
|
||||||
@@ -1648,51 +1523,38 @@ Substitution Description
|
|||||||
no template will return null value. See
|
no template will return null value. See
|
||||||
https://strftime.org/ for help on strftime
|
https://strftime.org/ for help on strftime
|
||||||
templates.
|
templates.
|
||||||
|
|
||||||
{modified.date} Photo's modification date in ISO format, e.g.
|
{modified.date} Photo's modification date in ISO format, e.g.
|
||||||
'2020-03-22'; uses creation date if photo is
|
'2020-03-22'; uses creation date if photo is
|
||||||
not modified
|
not modified
|
||||||
|
|
||||||
{modified.year} 4-digit year of photo modification time; uses
|
{modified.year} 4-digit year of photo modification time; uses
|
||||||
creation date if photo is not modified
|
creation date if photo is not modified
|
||||||
|
|
||||||
{modified.yy} 2-digit year of photo modification time; uses
|
{modified.yy} 2-digit year of photo modification time; uses
|
||||||
creation date if photo is not modified
|
creation date if photo is not modified
|
||||||
|
|
||||||
{modified.mm} 2-digit month of the photo modification time
|
{modified.mm} 2-digit month of the photo modification time
|
||||||
(zero padded); uses creation date if photo is
|
(zero padded); uses creation date if photo is
|
||||||
not modified
|
not modified
|
||||||
|
|
||||||
{modified.month} Month name in user's locale of the photo
|
{modified.month} Month name in user's locale of the photo
|
||||||
modification time; uses creation date if photo
|
modification time; uses creation date if photo
|
||||||
is not modified
|
is not modified
|
||||||
|
|
||||||
{modified.mon} Month abbreviation in the user's locale of the
|
{modified.mon} Month abbreviation in the user's locale of the
|
||||||
photo modification time; uses creation date if
|
photo modification time; uses creation date if
|
||||||
photo is not modified
|
photo is not modified
|
||||||
|
|
||||||
{modified.dd} 2-digit day of the month (zero padded) of the
|
{modified.dd} 2-digit day of the month (zero padded) of the
|
||||||
photo modification time; uses creation date if
|
photo modification time; uses creation date if
|
||||||
photo is not modified
|
photo is not modified
|
||||||
|
|
||||||
{modified.dow} Day of week in user's locale of the photo
|
{modified.dow} Day of week in user's locale of the photo
|
||||||
modification time; uses creation date if photo
|
modification time; uses creation date if photo
|
||||||
is not modified
|
is not modified
|
||||||
|
|
||||||
{modified.doy} 3-digit day of year (e.g Julian day) of photo
|
{modified.doy} 3-digit day of year (e.g Julian day) of photo
|
||||||
modification time, starting from 1 (zero
|
modification time, starting from 1 (zero
|
||||||
padded); uses creation date if photo is not
|
padded); uses creation date if photo is not
|
||||||
modified
|
modified
|
||||||
|
|
||||||
{modified.hour} 2-digit hour of the photo modification time;
|
{modified.hour} 2-digit hour of the photo modification time;
|
||||||
uses creation date if photo is not modified
|
uses creation date if photo is not modified
|
||||||
|
|
||||||
{modified.min} 2-digit minute of the photo modification time;
|
{modified.min} 2-digit minute of the photo modification time;
|
||||||
uses creation date if photo is not modified
|
uses creation date if photo is not modified
|
||||||
|
|
||||||
{modified.sec} 2-digit second of the photo modification time;
|
{modified.sec} 2-digit second of the photo modification time;
|
||||||
uses creation date if photo is not modified
|
uses creation date if photo is not modified
|
||||||
|
|
||||||
{modified.strftime} Apply strftime template to file modification
|
{modified.strftime} Apply strftime template to file modification
|
||||||
date/time. Should be used in form
|
date/time. Should be used in form
|
||||||
{modified.strftime,TEMPLATE} where TEMPLATE is
|
{modified.strftime,TEMPLATE} where TEMPLATE is
|
||||||
@@ -1703,28 +1565,21 @@ Substitution Description
|
|||||||
creation date if photo is not modified. See
|
creation date if photo is not modified. See
|
||||||
https://strftime.org/ for help on strftime
|
https://strftime.org/ for help on strftime
|
||||||
templates.
|
templates.
|
||||||
|
|
||||||
{today.date} Current date in iso format, e.g. '2020-03-22'
|
{today.date} Current date in iso format, e.g. '2020-03-22'
|
||||||
{today.year} 4-digit year of current date
|
{today.year} 4-digit year of current date
|
||||||
{today.yy} 2-digit year of current date
|
{today.yy} 2-digit year of current date
|
||||||
{today.mm} 2-digit month of the current date (zero
|
{today.mm} 2-digit month of the current date (zero
|
||||||
padded)
|
padded)
|
||||||
|
|
||||||
{today.month} Month name in user's locale of the current
|
{today.month} Month name in user's locale of the current
|
||||||
date
|
date
|
||||||
|
|
||||||
{today.mon} Month abbreviation in the user's locale of the
|
{today.mon} Month abbreviation in the user's locale of the
|
||||||
current date
|
current date
|
||||||
|
|
||||||
{today.dd} 2-digit day of the month (zero padded) of
|
{today.dd} 2-digit day of the month (zero padded) of
|
||||||
current date
|
current date
|
||||||
|
|
||||||
{today.dow} Day of week in user's locale of the current
|
{today.dow} Day of week in user's locale of the current
|
||||||
date
|
date
|
||||||
|
|
||||||
{today.doy} 3-digit day of year (e.g Julian day) of
|
{today.doy} 3-digit day of year (e.g Julian day) of
|
||||||
current date, starting from 1 (zero padded)
|
current date, starting from 1 (zero padded)
|
||||||
|
|
||||||
{today.hour} 2-digit hour of the current date
|
{today.hour} 2-digit hour of the current date
|
||||||
{today.min} 2-digit minute of the current date
|
{today.min} 2-digit minute of the current date
|
||||||
{today.sec} 2-digit second of the current date
|
{today.sec} 2-digit second of the current date
|
||||||
@@ -1737,70 +1592,51 @@ Substitution Description
|
|||||||
no template will return null value. See
|
no template will return null value. See
|
||||||
https://strftime.org/ for help on strftime
|
https://strftime.org/ for help on strftime
|
||||||
templates.
|
templates.
|
||||||
|
|
||||||
{place.name} Place name from the photo's reverse
|
{place.name} Place name from the photo's reverse
|
||||||
geolocation data, as displayed in Photos
|
geolocation data, as displayed in Photos
|
||||||
|
|
||||||
{place.country_code} The ISO country code from the photo's reverse
|
{place.country_code} The ISO country code from the photo's reverse
|
||||||
geolocation data
|
geolocation data
|
||||||
|
|
||||||
{place.name.country} Country name from the photo's reverse
|
{place.name.country} Country name from the photo's reverse
|
||||||
geolocation data
|
geolocation data
|
||||||
|
|
||||||
{place.name.state_province} State or province name from the photo's
|
{place.name.state_province} State or province name from the photo's
|
||||||
reverse geolocation data
|
reverse geolocation data
|
||||||
|
|
||||||
{place.name.city} City or locality name from the photo's reverse
|
{place.name.city} City or locality name from the photo's reverse
|
||||||
geolocation data
|
geolocation data
|
||||||
|
|
||||||
{place.name.area_of_interest} Area of interest name (e.g. landmark or public
|
{place.name.area_of_interest} Area of interest name (e.g. landmark or public
|
||||||
place) from the photo's reverse geolocation
|
place) from the photo's reverse geolocation
|
||||||
data
|
data
|
||||||
|
|
||||||
{place.address} Postal address from the photo's reverse
|
{place.address} Postal address from the photo's reverse
|
||||||
geolocation data, e.g. '2007 18th St NW,
|
geolocation data, e.g. '2007 18th St NW,
|
||||||
Washington, DC 20009, United States'
|
Washington, DC 20009, United States'
|
||||||
|
|
||||||
{place.address.street} Street part of the postal address, e.g. '2007
|
{place.address.street} Street part of the postal address, e.g. '2007
|
||||||
18th St NW'
|
18th St NW'
|
||||||
|
|
||||||
{place.address.city} City part of the postal address, e.g.
|
{place.address.city} City part of the postal address, e.g.
|
||||||
'Washington'
|
'Washington'
|
||||||
|
|
||||||
{place.address.state_province} State/province part of the postal address,
|
{place.address.state_province} State/province part of the postal address,
|
||||||
e.g. 'DC'
|
e.g. 'DC'
|
||||||
|
|
||||||
{place.address.postal_code} Postal code part of the postal address, e.g.
|
{place.address.postal_code} Postal code part of the postal address, e.g.
|
||||||
'20009'
|
'20009'
|
||||||
|
|
||||||
{place.address.country} Country name of the postal address, e.g.
|
{place.address.country} Country name of the postal address, e.g.
|
||||||
'United States'
|
'United States'
|
||||||
|
|
||||||
{place.address.country_code} ISO country code of the postal address, e.g.
|
{place.address.country_code} ISO country code of the postal address, e.g.
|
||||||
'US'
|
'US'
|
||||||
|
|
||||||
{searchinfo.season} Season of the year associated with a photo,
|
{searchinfo.season} Season of the year associated with a photo,
|
||||||
e.g. 'Summer'; (Photos 5+ only, applied
|
e.g. 'Summer'; (Photos 5+ only, applied
|
||||||
automatically by Photos' image categorization
|
automatically by Photos' image categorization
|
||||||
algorithms).
|
algorithms).
|
||||||
|
|
||||||
{exif.camera_make} Camera make from original photo's EXIF
|
{exif.camera_make} Camera make from original photo's EXIF
|
||||||
information as imported by Photos, e.g.
|
information as imported by Photos, e.g.
|
||||||
'Apple'
|
'Apple'
|
||||||
|
|
||||||
{exif.camera_model} Camera model from original photo's EXIF
|
{exif.camera_model} Camera model from original photo's EXIF
|
||||||
information as imported by Photos, e.g.
|
information as imported by Photos, e.g.
|
||||||
'iPhone 6s'
|
'iPhone 6s'
|
||||||
|
|
||||||
{exif.lens_model} Lens model from original photo's EXIF
|
{exif.lens_model} Lens model from original photo's EXIF
|
||||||
information as imported by Photos, e.g.
|
information as imported by Photos, e.g.
|
||||||
'iPhone 6s back camera 4.15mm f/2.2'
|
'iPhone 6s back camera 4.15mm f/2.2'
|
||||||
|
|
||||||
{uuid} Photo's internal universally unique identifier
|
{uuid} Photo's internal universally unique identifier
|
||||||
(UUID) for the photo, a 36-character string
|
(UUID) for the photo, a 36-character string
|
||||||
unique to the photo, e.g.
|
unique to the photo, e.g.
|
||||||
'128FB4C6-0B16-4E7D-9108-FB2E90DA1546'
|
'128FB4C6-0B16-4E7D-9108-FB2E90DA1546'
|
||||||
|
|
||||||
{id} A unique number for the photo based on its
|
{id} A unique number for the photo based on its
|
||||||
primary key in the Photos database. A
|
primary key in the Photos database. A
|
||||||
sequential integer, e.g. 1, 2, 3...etc. Each
|
sequential integer, e.g. 1, 2, 3...etc. Each
|
||||||
@@ -1811,7 +1647,6 @@ Substitution Description
|
|||||||
5-digit integer and pad with zeros, use
|
5-digit integer and pad with zeros, use
|
||||||
'{id:05d}' which results in 00001, 00002,
|
'{id:05d}' which results in 00001, 00002,
|
||||||
00003...etc.
|
00003...etc.
|
||||||
|
|
||||||
{album_seq} An integer, starting at 0, indicating the
|
{album_seq} An integer, starting at 0, indicating the
|
||||||
photo's index (sequence) in the containing
|
photo's index (sequence) in the containing
|
||||||
album. Only valid when used in a '--filename'
|
album. Only valid when used in a '--filename'
|
||||||
@@ -1831,7 +1666,6 @@ Substitution Description
|
|||||||
This may result in incorrect sequences if you
|
This may result in incorrect sequences if you
|
||||||
have duplicate albums with the same name; see
|
have duplicate albums with the same name; see
|
||||||
also '{folder_album_seq}'.
|
also '{folder_album_seq}'.
|
||||||
|
|
||||||
{folder_album_seq} An integer, starting at 0, indicating the
|
{folder_album_seq} An integer, starting at 0, indicating the
|
||||||
photo's index (sequence) in the containing
|
photo's index (sequence) in the containing
|
||||||
album and folder path. Only valid when used in
|
album and folder path. Only valid when used in
|
||||||
@@ -1852,7 +1686,6 @@ Substitution Description
|
|||||||
incorrect sequences if you have duplicate
|
incorrect sequences if you have duplicate
|
||||||
albums with the same name in the same folder;
|
albums with the same name in the same folder;
|
||||||
see also '{album_seq}'.
|
see also '{album_seq}'.
|
||||||
|
|
||||||
{comma} A comma: ','
|
{comma} A comma: ','
|
||||||
{semicolon} A semicolon: ';'
|
{semicolon} A semicolon: ';'
|
||||||
{questionmark} A question mark: '?'
|
{questionmark} A question mark: '?'
|
||||||
@@ -1867,7 +1700,7 @@ Substitution Description
|
|||||||
{lf} A line feed: '\n', alias for {newline}
|
{lf} A line feed: '\n', alias for {newline}
|
||||||
{cr} A carriage return: '\r'
|
{cr} A carriage return: '\r'
|
||||||
{crlf} a carriage return + line feed: '\r\n'
|
{crlf} a carriage return + line feed: '\r\n'
|
||||||
{osxphotos_version} The osxphotos version, e.g. '0.42.67'
|
{osxphotos_version} The osxphotos version, e.g. '0.42.68'
|
||||||
{osxphotos_cmd_line} The full command line used to run osxphotos
|
{osxphotos_cmd_line} The full command line used to run osxphotos
|
||||||
|
|
||||||
The following substitutions may result in multiple values. Thus if specified for
|
The following substitutions may result in multiple values. Thus if specified for
|
||||||
@@ -1882,7 +1715,6 @@ Substitution Description
|
|||||||
{folder_album} Folder path + album photo is contained in. e.g.
|
{folder_album} Folder path + album photo is contained in. e.g.
|
||||||
'Folder/Subfolder/Album' or just 'Album' if no
|
'Folder/Subfolder/Album' or just 'Album' if no
|
||||||
enclosing folder
|
enclosing folder
|
||||||
|
|
||||||
{keyword} Keyword(s) assigned to photo
|
{keyword} Keyword(s) assigned to photo
|
||||||
{person} Person(s) / face(s) in a photo
|
{person} Person(s) / face(s) in a photo
|
||||||
{label} Image categorization label associated with a photo
|
{label} Image categorization label associated with a photo
|
||||||
@@ -1891,11 +1723,9 @@ Substitution Description
|
|||||||
categorize images. These are not the same as
|
categorize images. These are not the same as
|
||||||
{keyword} which refers to the user-defined
|
{keyword} which refers to the user-defined
|
||||||
keywords/tags applied in Photos.
|
keywords/tags applied in Photos.
|
||||||
|
|
||||||
{label_normalized} All lower case version of 'label' (Photos 5+ only)
|
{label_normalized} All lower case version of 'label' (Photos 5+ only)
|
||||||
{comment} Comment(s) on shared Photos; format is 'Person name:
|
{comment} Comment(s) on shared Photos; format is 'Person name:
|
||||||
comment text' (Photos 5+ only)
|
comment text' (Photos 5+ only)
|
||||||
|
|
||||||
{exiftool} Format: '{exiftool:GROUP:TAGNAME}'; use exiftool
|
{exiftool} Format: '{exiftool:GROUP:TAGNAME}'; use exiftool
|
||||||
(https://exiftool.org) to extract metadata, in form
|
(https://exiftool.org) to extract metadata, in form
|
||||||
GROUP:TAGNAME, from image. E.g.
|
GROUP:TAGNAME, from image. E.g.
|
||||||
@@ -1905,24 +1735,19 @@ Substitution Description
|
|||||||
names. You must specify group (e.g. EXIF, IPTC, etc)
|
names. You must specify group (e.g. EXIF, IPTC, etc)
|
||||||
as used in `exiftool -G`. exiftool must be installed
|
as used in `exiftool -G`. exiftool must be installed
|
||||||
in the path to use this template.
|
in the path to use this template.
|
||||||
|
|
||||||
{searchinfo.holiday} Holiday names associated with a photo, e.g.
|
{searchinfo.holiday} Holiday names associated with a photo, e.g.
|
||||||
'Christmas Day'; (Photos 5+ only, applied
|
'Christmas Day'; (Photos 5+ only, applied
|
||||||
automatically by Photos' image categorization
|
automatically by Photos' image categorization
|
||||||
algorithms).
|
algorithms).
|
||||||
|
|
||||||
{searchinfo.activity} Activities associated with a photo, e.g. 'Sporting
|
{searchinfo.activity} Activities associated with a photo, e.g. 'Sporting
|
||||||
Event'; (Photos 5+ only, applied automatically by
|
Event'; (Photos 5+ only, applied automatically by
|
||||||
Photos' image categorization algorithms).
|
Photos' image categorization algorithms).
|
||||||
|
|
||||||
{searchinfo.venue} Venues associated with a photo, e.g. name of
|
{searchinfo.venue} Venues associated with a photo, e.g. name of
|
||||||
restaurant; (Photos 5+ only, applied automatically by
|
restaurant; (Photos 5+ only, applied automatically by
|
||||||
Photos' image categorization algorithms).
|
Photos' image categorization algorithms).
|
||||||
|
|
||||||
{searchinfo.venue_type} Venue types associated with a photo, e.g.
|
{searchinfo.venue_type} Venue types associated with a photo, e.g.
|
||||||
'Restaurant'; (Photos 5+ only, applied automatically
|
'Restaurant'; (Photos 5+ only, applied automatically
|
||||||
by Photos' image categorization algorithms).
|
by Photos' image categorization algorithms).
|
||||||
|
|
||||||
{photo} Provides direct access to the PhotoInfo object for
|
{photo} Provides direct access to the PhotoInfo object for
|
||||||
the photo. Must be used in format '{photo.property}'
|
the photo. Must be used in format '{photo.property}'
|
||||||
where 'property' represents a PhotoInfo property. For
|
where 'property' represents a PhotoInfo property. For
|
||||||
@@ -1934,12 +1759,23 @@ Substitution Description
|
|||||||
underlying PhotoInfo class. See
|
underlying PhotoInfo class. See
|
||||||
https://rhettbull.github.io/osxphotos/ for additional
|
https://rhettbull.github.io/osxphotos/ for additional
|
||||||
documentation on the PhotoInfo class.
|
documentation on the PhotoInfo class.
|
||||||
|
{detected_text} List of text strings found in the image after
|
||||||
|
performing text detection. Using '{detected_text}'
|
||||||
|
will cause osxphotos to perform text detection 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. 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
|
{shell_quote} Use in form '{shell_quote,TEMPLATE}'; quotes the
|
||||||
rendered TEMPLATE value(s) for safe usage in the
|
rendered TEMPLATE value(s) for safe usage in the
|
||||||
shell, e.g. My file.jpeg => 'My file.jpeg'; only adds
|
shell, e.g. My file.jpeg => 'My file.jpeg'; only adds
|
||||||
quotes if needed.
|
quotes if needed.
|
||||||
|
|
||||||
{function} Execute a python function from an external file and
|
{function} Execute a python function from an external file and
|
||||||
use return value as template substitution. Use in
|
use return value as template substitution. Use in
|
||||||
format: {function:file.py::function_name} where
|
format: {function:file.py::function_name} where
|
||||||
@@ -1950,7 +1786,6 @@ Substitution Description
|
|||||||
/blob/master/examples/template_function.py for an
|
/blob/master/examples/template_function.py for an
|
||||||
example of how to implement a template function.
|
example of how to implement a template function.
|
||||||
|
|
||||||
|
|
||||||
The following substitutions are file or directory paths. You can access various
|
The following substitutions are file or directory paths. You can access various
|
||||||
parts of the path using the following modifiers:
|
parts of the path using the following modifiers:
|
||||||
|
|
||||||
@@ -1985,41 +1820,29 @@ exported All exported files
|
|||||||
new When used with '--update', all newly exported files
|
new When used with '--update', all newly exported files
|
||||||
updated When used with '--update', all files which were
|
updated When used with '--update', all files which were
|
||||||
previously exported but updated this time
|
previously exported but updated this time
|
||||||
|
|
||||||
skipped When used with '--update', all files which were
|
skipped When used with '--update', all files which were
|
||||||
skipped (because they were previously exported and
|
skipped (because they were previously exported and
|
||||||
didn't change)
|
didn't change)
|
||||||
|
|
||||||
missing All files which were not exported because they were
|
missing All files which were not exported because they were
|
||||||
missing from the Photos library
|
missing from the Photos library
|
||||||
|
|
||||||
exif_updated When used with '--exiftool', all files on which
|
exif_updated When used with '--exiftool', all files on which
|
||||||
exiftool updated the metadata
|
exiftool updated the metadata
|
||||||
|
|
||||||
touched When used with '--touch-file', all files where the
|
touched When used with '--touch-file', all files where the
|
||||||
date was touched
|
date was touched
|
||||||
|
|
||||||
converted_to_jpeg When used with '--convert-to-jpeg', all files which
|
converted_to_jpeg When used with '--convert-to-jpeg', all files which
|
||||||
were converted to jpeg
|
were converted to jpeg
|
||||||
|
|
||||||
sidecar_json_written When used with '--sidecar json', all JSON sidecar
|
sidecar_json_written When used with '--sidecar json', all JSON sidecar
|
||||||
files which were written
|
files which were written
|
||||||
|
|
||||||
sidecar_json_skipped When used with '--sidecar json' and '--update', all
|
sidecar_json_skipped When used with '--sidecar json' and '--update', all
|
||||||
JSON sidecar files which were skipped
|
JSON sidecar files which were skipped
|
||||||
|
|
||||||
sidecar_exiftool_written When used with '--sidecar exiftool', all exiftool
|
sidecar_exiftool_written When used with '--sidecar exiftool', all exiftool
|
||||||
sidecar files which were written
|
sidecar files which were written
|
||||||
|
|
||||||
sidecar_exiftool_skipped When used with '--sidecar exiftool' and '--update,
|
sidecar_exiftool_skipped When used with '--sidecar exiftool' and '--update,
|
||||||
all exiftool sidecar files which were skipped
|
all exiftool sidecar files which were skipped
|
||||||
|
|
||||||
sidecar_xmp_written When used with '--sidecar xmp', all XMP sidecar
|
sidecar_xmp_written When used with '--sidecar xmp', all XMP sidecar
|
||||||
files which were written
|
files which were written
|
||||||
|
|
||||||
sidecar_xmp_skipped When used with '--sidecar xmp' and '--update', all
|
sidecar_xmp_skipped When used with '--sidecar xmp' and '--update', all
|
||||||
XMP sidecar files which were skipped
|
XMP sidecar files which were skipped
|
||||||
|
|
||||||
error All files which produced an error during export
|
error All files which produced an error during export
|
||||||
|
|
||||||
In addition to all normal template fields, the template fields '{filepath}' and
|
In addition to all normal template fields, the template fields '{filepath}' and
|
||||||
@@ -3730,7 +3553,7 @@ The following template field substitutions are availabe for use the templating s
|
|||||||
|{lf}|A line feed: '\n', alias for {newline}|
|
|{lf}|A line feed: '\n', alias for {newline}|
|
||||||
|{cr}|A carriage return: '\r'|
|
|{cr}|A carriage return: '\r'|
|
||||||
|{crlf}|a carriage return + line feed: '\r\n'|
|
|{crlf}|a carriage return + line feed: '\r\n'|
|
||||||
|{osxphotos_version}|The osxphotos version, e.g. '0.42.67'|
|
|{osxphotos_version}|The osxphotos version, e.g. '0.42.68'|
|
||||||
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|
||||||
|{album}|Album(s) photo is contained in|
|
|{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|
|
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|
|
||||||
@@ -3745,6 +3568,7 @@ The following template field substitutions are availabe for use the templating s
|
|||||||
|{searchinfo.venue}|Venues associated with a photo, e.g. name of restaurant; (Photos 5+ only, applied automatically by Photos' image categorization algorithms).|
|
|{searchinfo.venue}|Venues associated with a photo, e.g. name of restaurant; (Photos 5+ only, applied automatically by Photos' image categorization algorithms).|
|
||||||
|{searchinfo.venue_type}|Venue types associated with a photo, e.g. 'Restaurant'; (Photos 5+ only, applied automatically by Photos' image categorization algorithms).|
|
|{searchinfo.venue_type}|Venue types associated with a photo, e.g. 'Restaurant'; (Photos 5+ only, applied automatically by Photos' image categorization algorithms).|
|
||||||
|{photo}|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 https://rhettbull.github.io/osxphotos/ for additional documentation on the PhotoInfo class.|
|
|{photo}|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 https://rhettbull.github.io/osxphotos/ for additional documentation on the PhotoInfo class.|
|
||||||
|
|{detected_text}|List of text strings found in the image after performing text detection. Using '{detected_text}' will cause osxphotos to perform text detection 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. 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.|
|
|{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.|
|
||||||
|{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.|
|
|{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.|
|
||||||
<!-- OSXPHOTOS-TEMPLATE-TABLE:END -->
|
<!-- OSXPHOTOS-TEMPLATE-TABLE:END -->
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.42.68"
|
__version__ = "0.42.69"
|
||||||
|
|||||||
100
osxphotos/cli.py
100
osxphotos/cli.py
@@ -1796,6 +1796,7 @@ def export(
|
|||||||
export_dir=dest,
|
export_dir=dest,
|
||||||
dry_run=dry_run,
|
dry_run=dry_run,
|
||||||
exiftool_path=exiftool_path,
|
exiftool_path=exiftool_path,
|
||||||
|
export_db=export_db,
|
||||||
)
|
)
|
||||||
|
|
||||||
if album_export and export_results.exported:
|
if album_export and export_results.exported:
|
||||||
@@ -1865,6 +1866,7 @@ def export(
|
|||||||
finder_tag_template=finder_tag_template,
|
finder_tag_template=finder_tag_template,
|
||||||
strip=strip,
|
strip=strip,
|
||||||
export_dir=dest,
|
export_dir=dest,
|
||||||
|
export_db=export_db,
|
||||||
)
|
)
|
||||||
results.xattr_written.extend(tags_written)
|
results.xattr_written.extend(tags_written)
|
||||||
results.xattr_skipped.extend(tags_skipped)
|
results.xattr_skipped.extend(tags_skipped)
|
||||||
@@ -1876,6 +1878,7 @@ def export(
|
|||||||
xattr_template,
|
xattr_template,
|
||||||
strip=strip,
|
strip=strip,
|
||||||
export_dir=dest,
|
export_dir=dest,
|
||||||
|
export_db=export_db,
|
||||||
)
|
)
|
||||||
results.xattr_written.extend(xattr_written)
|
results.xattr_written.extend(xattr_written)
|
||||||
results.xattr_skipped.extend(xattr_skipped)
|
results.xattr_skipped.extend(xattr_skipped)
|
||||||
@@ -2538,10 +2541,22 @@ def export_photo(
|
|||||||
sidecar_flags |= SIDECAR_EXIFTOOL
|
sidecar_flags |= SIDECAR_EXIFTOOL
|
||||||
|
|
||||||
rendered_suffix = _render_suffix_template(
|
rendered_suffix = _render_suffix_template(
|
||||||
original_suffix, "original_suffix", "--original-suffix", strip, dest, photo
|
original_suffix,
|
||||||
|
"original_suffix",
|
||||||
|
"--original-suffix",
|
||||||
|
strip,
|
||||||
|
dest,
|
||||||
|
photo,
|
||||||
|
export_db,
|
||||||
)
|
)
|
||||||
rendered_preview_suffix = _render_suffix_template(
|
rendered_preview_suffix = _render_suffix_template(
|
||||||
preview_suffix, "preview_suffix", "--preview-suffix", strip, dest, photo
|
preview_suffix,
|
||||||
|
"preview_suffix",
|
||||||
|
"--preview-suffix",
|
||||||
|
strip,
|
||||||
|
dest,
|
||||||
|
photo,
|
||||||
|
export_db,
|
||||||
)
|
)
|
||||||
|
|
||||||
# if download_missing and the photo is missing or path doesn't exist,
|
# if download_missing and the photo is missing or path doesn't exist,
|
||||||
@@ -2557,11 +2572,24 @@ def export_photo(
|
|||||||
|
|
||||||
results = ExportResults()
|
results = ExportResults()
|
||||||
dest_paths = get_dirnames_from_template(
|
dest_paths = get_dirnames_from_template(
|
||||||
photo, directory, export_by_date, dest, dry_run, strip=strip, edited=False
|
photo,
|
||||||
|
directory,
|
||||||
|
export_by_date,
|
||||||
|
dest,
|
||||||
|
dry_run,
|
||||||
|
strip=strip,
|
||||||
|
edited=False,
|
||||||
|
export_db=export_db,
|
||||||
)
|
)
|
||||||
for dest_path in dest_paths:
|
for dest_path in dest_paths:
|
||||||
filenames = get_filenames_from_template(
|
filenames = get_filenames_from_template(
|
||||||
photo, filename_template, dest, dest_path, original_name, strip=strip
|
photo,
|
||||||
|
filename_template,
|
||||||
|
dest,
|
||||||
|
dest_path,
|
||||||
|
original_name,
|
||||||
|
strip=strip,
|
||||||
|
export_db=export_db,
|
||||||
)
|
)
|
||||||
|
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
@@ -2632,12 +2660,26 @@ def export_photo(
|
|||||||
|
|
||||||
if export_edited and photo.hasadjustments:
|
if export_edited and photo.hasadjustments:
|
||||||
dest_paths = get_dirnames_from_template(
|
dest_paths = get_dirnames_from_template(
|
||||||
photo, directory, export_by_date, dest, dry_run, strip=strip, edited=True
|
photo,
|
||||||
|
directory,
|
||||||
|
export_by_date,
|
||||||
|
dest,
|
||||||
|
dry_run,
|
||||||
|
strip=strip,
|
||||||
|
edited=True,
|
||||||
|
export_db=export_db,
|
||||||
)
|
)
|
||||||
for dest_path in dest_paths:
|
for dest_path in dest_paths:
|
||||||
# if export-edited, also export the edited version
|
# if export-edited, also export the edited version
|
||||||
edited_filenames = get_filenames_from_template(
|
edited_filenames = get_filenames_from_template(
|
||||||
photo, filename_template, dest, dest_path, original_name, strip=strip, edited=True
|
photo,
|
||||||
|
filename_template,
|
||||||
|
dest,
|
||||||
|
dest_path,
|
||||||
|
original_name,
|
||||||
|
strip=strip,
|
||||||
|
edited=True,
|
||||||
|
export_db=export_db,
|
||||||
)
|
)
|
||||||
for edited_filename in edited_filenames:
|
for edited_filename in edited_filenames:
|
||||||
edited_filename = pathlib.Path(edited_filename)
|
edited_filename = pathlib.Path(edited_filename)
|
||||||
@@ -2674,6 +2716,7 @@ def export_photo(
|
|||||||
strip,
|
strip,
|
||||||
dest,
|
dest,
|
||||||
photo,
|
photo,
|
||||||
|
export_db,
|
||||||
)
|
)
|
||||||
edited_filename = (
|
edited_filename = (
|
||||||
f"{edited_filename.stem}{rendered_edited_suffix}{edited_ext}"
|
f"{edited_filename.stem}{rendered_edited_suffix}{edited_ext}"
|
||||||
@@ -2729,7 +2772,9 @@ def export_photo(
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
def _render_suffix_template(suffix_template, var_name, option_name, strip, dest, photo):
|
def _render_suffix_template(
|
||||||
|
suffix_template, var_name, option_name, strip, dest, photo, export_db
|
||||||
|
):
|
||||||
"""render suffix template
|
"""render suffix template
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -2739,7 +2784,9 @@ def _render_suffix_template(suffix_template, var_name, option_name, strip, dest,
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
options = RenderOptions(filename=True, strip=strip, export_dir=dest)
|
options = RenderOptions(
|
||||||
|
filename=True, strip=strip, export_dir=dest, exportdb=export_db
|
||||||
|
)
|
||||||
rendered_suffix, unmatched = photo.render_template(suffix_template, options)
|
rendered_suffix, unmatched = photo.render_template(suffix_template, options)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise click.BadOptionUsage(
|
raise click.BadOptionUsage(
|
||||||
@@ -2848,7 +2895,9 @@ def export_photo_to_directory(
|
|||||||
results.missing.append(str(pathlib.Path(dest_path) / filename))
|
results.missing.append(str(pathlib.Path(dest_path) / filename))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
render_options = RenderOptions(export_dir=export_dir, dest_path=dest_path)
|
render_options = RenderOptions(
|
||||||
|
export_dir=export_dir, dest_path=dest_path, exportdb=export_db
|
||||||
|
)
|
||||||
|
|
||||||
tries = 0
|
tries = 0
|
||||||
while tries <= retry:
|
while tries <= retry:
|
||||||
@@ -2960,6 +3009,7 @@ def get_filenames_from_template(
|
|||||||
original_name,
|
original_name,
|
||||||
strip=False,
|
strip=False,
|
||||||
edited=False,
|
edited=False,
|
||||||
|
export_db=None,
|
||||||
):
|
):
|
||||||
"""get list of export filenames for a photo
|
"""get list of export filenames for a photo
|
||||||
|
|
||||||
@@ -2987,6 +3037,7 @@ def get_filenames_from_template(
|
|||||||
edited_version=edited,
|
edited_version=edited,
|
||||||
export_dir=export_dir,
|
export_dir=export_dir,
|
||||||
dest_path=dest_path,
|
dest_path=dest_path,
|
||||||
|
exportdb=export_db,
|
||||||
)
|
)
|
||||||
filenames, unmatched = photo.render_template(filename_template, options)
|
filenames, unmatched = photo.render_template(filename_template, options)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
@@ -3011,7 +3062,14 @@ def get_filenames_from_template(
|
|||||||
|
|
||||||
|
|
||||||
def get_dirnames_from_template(
|
def get_dirnames_from_template(
|
||||||
photo, directory, export_by_date, dest, dry_run, strip=False, edited=False
|
photo,
|
||||||
|
directory,
|
||||||
|
export_by_date,
|
||||||
|
dest,
|
||||||
|
dry_run,
|
||||||
|
strip=False,
|
||||||
|
edited=False,
|
||||||
|
export_db=None,
|
||||||
):
|
):
|
||||||
"""get list of directories to export a photo into, creates directories if they don't exist
|
"""get list of directories to export a photo into, creates directories if they don't exist
|
||||||
|
|
||||||
@@ -3042,7 +3100,9 @@ def get_dirnames_from_template(
|
|||||||
elif directory:
|
elif directory:
|
||||||
# got a directory template, render it and check results are valid
|
# got a directory template, render it and check results are valid
|
||||||
try:
|
try:
|
||||||
options = RenderOptions(dirname=True, strip=strip, edited_version=edited)
|
options = RenderOptions(
|
||||||
|
dirname=True, strip=strip, edited_version=edited, exportdb=export_db
|
||||||
|
)
|
||||||
dirnames, unmatched = photo.render_template(directory, options)
|
dirnames, unmatched = photo.render_template(directory, options)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise click.BadOptionUsage(
|
raise click.BadOptionUsage(
|
||||||
@@ -3325,6 +3385,7 @@ def write_finder_tags(
|
|||||||
finder_tag_template=None,
|
finder_tag_template=None,
|
||||||
strip=False,
|
strip=False,
|
||||||
export_dir=None,
|
export_dir=None,
|
||||||
|
export_db=None,
|
||||||
):
|
):
|
||||||
"""Write Finder tags (extended attributes) to files; only writes attributes if attributes on file differ from what would be written
|
"""Write Finder tags (extended attributes) to files; only writes attributes if attributes on file differ from what would be written
|
||||||
|
|
||||||
@@ -3338,6 +3399,7 @@ def write_finder_tags(
|
|||||||
exiftool_merge_keywords: if True, include any keywords in the exif data of the source image as keywords
|
exiftool_merge_keywords: if True, include any keywords in the exif data of the source image as keywords
|
||||||
finder_tag_template: list of templates to evaluate for determining Finder tags
|
finder_tag_template: list of templates to evaluate for determining Finder tags
|
||||||
export_dir: value to use for {export_dir} template
|
export_dir: value to use for {export_dir} template
|
||||||
|
export_db: an ExportDB object
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(list of file paths that were updated with new Finder tags, list of file paths skipped because Finder tags didn't need updating)
|
(list of file paths that were updated with new Finder tags, list of file paths skipped because Finder tags didn't need updating)
|
||||||
@@ -3369,6 +3431,7 @@ def write_finder_tags(
|
|||||||
path_sep="/",
|
path_sep="/",
|
||||||
strip=strip,
|
strip=strip,
|
||||||
export_dir=export_dir,
|
export_dir=export_dir,
|
||||||
|
exportdb=export_db,
|
||||||
)
|
)
|
||||||
rendered, unmatched = photo.render_template(template_str, options)
|
rendered, unmatched = photo.render_template(template_str, options)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
@@ -3408,7 +3471,12 @@ def write_finder_tags(
|
|||||||
|
|
||||||
|
|
||||||
def write_extended_attributes(
|
def write_extended_attributes(
|
||||||
photo, files, xattr_template, strip=False, export_dir=None
|
photo,
|
||||||
|
files,
|
||||||
|
xattr_template,
|
||||||
|
strip=False,
|
||||||
|
export_dir=None,
|
||||||
|
export_db=None,
|
||||||
):
|
):
|
||||||
"""Writes extended attributes to exported files
|
"""Writes extended attributes to exported files
|
||||||
|
|
||||||
@@ -3416,6 +3484,7 @@ def write_extended_attributes(
|
|||||||
photo: a PhotoInfo object
|
photo: a PhotoInfo object
|
||||||
strip: xattr_template: list of tuples: (attribute name, attribute template)
|
strip: xattr_template: list of tuples: (attribute name, attribute template)
|
||||||
export_dir: value to use for {export_dir} template
|
export_dir: value to use for {export_dir} template
|
||||||
|
exportdb: an ExportDB object
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple(list of file paths that were updated with new attributes, list of file paths skipped because attributes didn't need updating)
|
tuple(list of file paths that were updated with new attributes, list of file paths skipped because attributes didn't need updating)
|
||||||
@@ -3429,6 +3498,7 @@ def write_extended_attributes(
|
|||||||
path_sep="/",
|
path_sep="/",
|
||||||
strip=strip,
|
strip=strip,
|
||||||
export_dir=export_dir,
|
export_dir=export_dir,
|
||||||
|
exportdb=export_db,
|
||||||
)
|
)
|
||||||
rendered, unmatched = photo.render_template(template_str, options)
|
rendered, unmatched = photo.render_template(template_str, options)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
@@ -3480,7 +3550,7 @@ def write_extended_attributes(
|
|||||||
|
|
||||||
|
|
||||||
def run_post_command(
|
def run_post_command(
|
||||||
photo, post_command, export_results, export_dir, dry_run, exiftool_path
|
photo, post_command, export_results, export_dir, dry_run, exiftool_path, export_db
|
||||||
):
|
):
|
||||||
# todo: pass in RenderOptions from export? (e.g. so it contains strip, etc?)
|
# todo: pass in RenderOptions from export? (e.g. so it contains strip, etc?)
|
||||||
# todo: need a shell_quote template type:
|
# todo: need a shell_quote template type:
|
||||||
@@ -3492,7 +3562,9 @@ def run_post_command(
|
|||||||
# some categories, like error, return a tuple of (file, error str)
|
# some categories, like error, return a tuple of (file, error str)
|
||||||
if isinstance(f, tuple):
|
if isinstance(f, tuple):
|
||||||
f = f[0]
|
f = f[0]
|
||||||
render_options = RenderOptions(export_dir=export_dir, filepath=f)
|
render_options = RenderOptions(
|
||||||
|
export_dir=export_dir, filepath=f, exportdb=export_db
|
||||||
|
)
|
||||||
template = PhotoTemplate(photo, exiftool_path=exiftool_path)
|
template = PhotoTemplate(photo, exiftool_path=exiftool_path)
|
||||||
command, _ = template.render(command_template, options=render_options)
|
command, _ = template.render(command_template, options=render_options)
|
||||||
command = command[0] if command else None
|
command = command[0] if command else None
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
""" Helper class for managing a database used by
|
""" Helper class for managing a database used by PhotoInfo.export for tracking state of exports and updates """
|
||||||
PhotoInfo.export for tracking state of exports and updates
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
@@ -12,13 +10,15 @@ from abc import ABC, abstractmethod
|
|||||||
from io import StringIO
|
from io import StringIO
|
||||||
from sqlite3 import Error
|
from sqlite3 import Error
|
||||||
|
|
||||||
|
from ._constants import OSXPHOTOS_EXPORT_DB
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
|
|
||||||
OSXPHOTOS_EXPORTDB_VERSION = "3.2"
|
OSXPHOTOS_EXPORTDB_VERSION = "4.0"
|
||||||
|
OSXPHOTOS_ABOUT_STRING = f"Created by osxphotos version {__version__} (https://github.com/RhetTbull/osxphotos) on {str(datetime.datetime.now())}"
|
||||||
|
|
||||||
|
|
||||||
class ExportDB_ABC(ABC):
|
class ExportDB_ABC(ABC):
|
||||||
""" abstract base class for ExportDB """
|
"""abstract base class for ExportDB"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_uuid_for_file(self, filename):
|
def get_uuid_for_file(self, filename):
|
||||||
@@ -88,6 +88,14 @@ class ExportDB_ABC(ABC):
|
|||||||
def get_previous_uuids(self):
|
def get_previous_uuids(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_detected_text_for_uuid(self, uuid, confidence):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def set_detected_text_for_uuid(self, uuid, text, confidence):
|
||||||
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def set_data(
|
def set_data(
|
||||||
self,
|
self,
|
||||||
@@ -104,7 +112,7 @@ class ExportDB_ABC(ABC):
|
|||||||
|
|
||||||
|
|
||||||
class ExportDBNoOp(ExportDB_ABC):
|
class ExportDBNoOp(ExportDB_ABC):
|
||||||
""" An ExportDB with NoOp methods """
|
"""An ExportDB with NoOp methods"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.was_created = True
|
self.was_created = True
|
||||||
@@ -162,6 +170,12 @@ class ExportDBNoOp(ExportDB_ABC):
|
|||||||
def get_previous_uuids(self):
|
def get_previous_uuids(self):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def get_detected_text_for_uuid(self, uuid, confidence):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def set_detected_text_for_uuid(self, uuid, text, confidence):
|
||||||
|
pass
|
||||||
|
|
||||||
def set_data(
|
def set_data(
|
||||||
self,
|
self,
|
||||||
filename,
|
filename,
|
||||||
@@ -177,23 +191,23 @@ class ExportDBNoOp(ExportDB_ABC):
|
|||||||
|
|
||||||
|
|
||||||
class ExportDB(ExportDB_ABC):
|
class ExportDB(ExportDB_ABC):
|
||||||
""" Interface to sqlite3 database used to store state information for osxphotos export command """
|
"""Interface to sqlite3 database used to store state information for osxphotos export command"""
|
||||||
|
|
||||||
def __init__(self, dbfile):
|
def __init__(self, dbfile):
|
||||||
""" dbfile: path to osxphotos export database file """
|
"""dbfile: path to osxphotos export database file"""
|
||||||
self._dbfile = dbfile
|
self._dbfile = dbfile
|
||||||
# _path is parent of the database
|
# _path is parent of the database
|
||||||
# all files referenced by get_/set_uuid_for_file will be converted to
|
# all files referenced by get_/set_uuid_for_file will be converted to
|
||||||
# relative paths to this parent _path
|
# relative paths to this parent _path
|
||||||
# this allows the entire export tree to be moved to a new disk/location
|
# this allows the entire export tree to be moved to a new disk/location
|
||||||
# whilst preserving the UUID to filename mappping
|
# whilst preserving the UUID to filename mapping
|
||||||
self._path = pathlib.Path(dbfile).parent
|
self._path = pathlib.Path(dbfile).parent
|
||||||
self._conn = self._open_export_db(dbfile)
|
self._conn = self._open_export_db(dbfile)
|
||||||
self._insert_run_info()
|
self._insert_run_info()
|
||||||
|
|
||||||
def get_uuid_for_file(self, filename):
|
def get_uuid_for_file(self, filename):
|
||||||
""" query database for filename and return UUID
|
"""query database for filename and return UUID
|
||||||
returns None if filename not found in database
|
returns None if filename not found in database
|
||||||
"""
|
"""
|
||||||
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
@@ -211,7 +225,7 @@ class ExportDB(ExportDB_ABC):
|
|||||||
return uuid
|
return uuid
|
||||||
|
|
||||||
def set_uuid_for_file(self, filename, uuid):
|
def set_uuid_for_file(self, filename, uuid):
|
||||||
""" set UUID of filename to uuid in the database """
|
"""set UUID of filename to uuid in the database"""
|
||||||
filename = str(pathlib.Path(filename).relative_to(self._path))
|
filename = str(pathlib.Path(filename).relative_to(self._path))
|
||||||
filename_normalized = filename.lower()
|
filename_normalized = filename.lower()
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
@@ -226,9 +240,9 @@ class ExportDB(ExportDB_ABC):
|
|||||||
logging.warning(e)
|
logging.warning(e)
|
||||||
|
|
||||||
def set_stat_orig_for_file(self, filename, stats):
|
def set_stat_orig_for_file(self, filename, stats):
|
||||||
""" set stat info for filename
|
"""set stat info for filename
|
||||||
filename: filename to set the stat info for
|
filename: filename to set the stat info for
|
||||||
stat: a tuple of length 3: mode, size, mtime """
|
stat: a tuple of length 3: mode, size, mtime"""
|
||||||
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
||||||
if len(stats) != 3:
|
if len(stats) != 3:
|
||||||
raise ValueError(f"expected 3 elements for stat, got {len(stats)}")
|
raise ValueError(f"expected 3 elements for stat, got {len(stats)}")
|
||||||
@@ -247,8 +261,8 @@ class ExportDB(ExportDB_ABC):
|
|||||||
logging.warning(e)
|
logging.warning(e)
|
||||||
|
|
||||||
def get_stat_orig_for_file(self, filename):
|
def get_stat_orig_for_file(self, filename):
|
||||||
""" get stat info for filename
|
"""get stat info for filename
|
||||||
returns: tuple of (mode, size, mtime)
|
returns: tuple of (mode, size, mtime)
|
||||||
"""
|
"""
|
||||||
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
@@ -272,21 +286,21 @@ class ExportDB(ExportDB_ABC):
|
|||||||
return stats
|
return stats
|
||||||
|
|
||||||
def set_stat_edited_for_file(self, filename, stats):
|
def set_stat_edited_for_file(self, filename, stats):
|
||||||
""" set stat info for edited version of image (in Photos' library)
|
"""set stat info for edited version of image (in Photos' library)
|
||||||
filename: filename to set the stat info for
|
filename: filename to set the stat info for
|
||||||
stat: a tuple of length 3: mode, size, mtime """
|
stat: a tuple of length 3: mode, size, mtime"""
|
||||||
return self._set_stat_for_file("edited", filename, stats)
|
return self._set_stat_for_file("edited", filename, stats)
|
||||||
|
|
||||||
def get_stat_edited_for_file(self, filename):
|
def get_stat_edited_for_file(self, filename):
|
||||||
""" get stat info for edited version of image (in Photos' library)
|
"""get stat info for edited version of image (in Photos' library)
|
||||||
filename: filename to set the stat info for
|
filename: filename to set the stat info for
|
||||||
stat: a tuple of length 3: mode, size, mtime """
|
stat: a tuple of length 3: mode, size, mtime"""
|
||||||
return self._get_stat_for_file("edited", filename)
|
return self._get_stat_for_file("edited", filename)
|
||||||
|
|
||||||
def set_stat_exif_for_file(self, filename, stats):
|
def set_stat_exif_for_file(self, filename, stats):
|
||||||
""" set stat info for filename (after exiftool has updated it)
|
"""set stat info for filename (after exiftool has updated it)
|
||||||
filename: filename to set the stat info for
|
filename: filename to set the stat info for
|
||||||
stat: a tuple of length 3: mode, size, mtime """
|
stat: a tuple of length 3: mode, size, mtime"""
|
||||||
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
||||||
if len(stats) != 3:
|
if len(stats) != 3:
|
||||||
raise ValueError(f"expected 3 elements for stat, got {len(stats)}")
|
raise ValueError(f"expected 3 elements for stat, got {len(stats)}")
|
||||||
@@ -305,8 +319,8 @@ class ExportDB(ExportDB_ABC):
|
|||||||
logging.warning(e)
|
logging.warning(e)
|
||||||
|
|
||||||
def get_stat_exif_for_file(self, filename):
|
def get_stat_exif_for_file(self, filename):
|
||||||
""" get stat info for filename (after exiftool has updated it)
|
"""get stat info for filename (after exiftool has updated it)
|
||||||
returns: tuple of (mode, size, mtime)
|
returns: tuple of (mode, size, mtime)
|
||||||
"""
|
"""
|
||||||
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
@@ -330,19 +344,19 @@ class ExportDB(ExportDB_ABC):
|
|||||||
return stats
|
return stats
|
||||||
|
|
||||||
def set_stat_converted_for_file(self, filename, stats):
|
def set_stat_converted_for_file(self, filename, stats):
|
||||||
""" set stat info for filename (after image converted to jpeg)
|
"""set stat info for filename (after image converted to jpeg)
|
||||||
filename: filename to set the stat info for
|
filename: filename to set the stat info for
|
||||||
stat: a tuple of length 3: mode, size, mtime """
|
stat: a tuple of length 3: mode, size, mtime"""
|
||||||
return self._set_stat_for_file("converted", filename, stats)
|
return self._set_stat_for_file("converted", filename, stats)
|
||||||
|
|
||||||
def get_stat_converted_for_file(self, filename):
|
def get_stat_converted_for_file(self, filename):
|
||||||
""" get stat info for filename (after jpeg conversion)
|
"""get stat info for filename (after jpeg conversion)
|
||||||
returns: tuple of (mode, size, mtime)
|
returns: tuple of (mode, size, mtime)
|
||||||
"""
|
"""
|
||||||
return self._get_stat_for_file("converted", filename)
|
return self._get_stat_for_file("converted", filename)
|
||||||
|
|
||||||
def get_info_for_uuid(self, uuid):
|
def get_info_for_uuid(self, uuid):
|
||||||
""" returns the info JSON struct for a UUID """
|
"""returns the info JSON struct for a UUID"""
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
try:
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
@@ -356,7 +370,7 @@ class ExportDB(ExportDB_ABC):
|
|||||||
return info
|
return info
|
||||||
|
|
||||||
def set_info_for_uuid(self, uuid, info):
|
def set_info_for_uuid(self, uuid, info):
|
||||||
""" sets the info JSON struct for a UUID """
|
"""sets the info JSON struct for a UUID"""
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
try:
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
@@ -369,7 +383,7 @@ class ExportDB(ExportDB_ABC):
|
|||||||
logging.warning(e)
|
logging.warning(e)
|
||||||
|
|
||||||
def get_exifdata_for_file(self, filename):
|
def get_exifdata_for_file(self, filename):
|
||||||
""" returns the exifdata JSON struct for a file """
|
"""returns the exifdata JSON struct for a file"""
|
||||||
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
try:
|
||||||
@@ -387,7 +401,7 @@ class ExportDB(ExportDB_ABC):
|
|||||||
return exifdata
|
return exifdata
|
||||||
|
|
||||||
def set_exifdata_for_file(self, filename, exifdata):
|
def set_exifdata_for_file(self, filename, exifdata):
|
||||||
""" sets the exifdata JSON struct for a file """
|
"""sets the exifdata JSON struct for a file"""
|
||||||
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
try:
|
||||||
@@ -401,7 +415,7 @@ class ExportDB(ExportDB_ABC):
|
|||||||
logging.warning(e)
|
logging.warning(e)
|
||||||
|
|
||||||
def get_sidecar_for_file(self, filename):
|
def get_sidecar_for_file(self, filename):
|
||||||
""" returns the sidecar data and signature for a file """
|
"""returns the sidecar data and signature for a file"""
|
||||||
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
try:
|
||||||
@@ -429,7 +443,7 @@ class ExportDB(ExportDB_ABC):
|
|||||||
return sidecar_data, sidecar_sig
|
return sidecar_data, sidecar_sig
|
||||||
|
|
||||||
def set_sidecar_for_file(self, filename, sidecar_data, sidecar_sig):
|
def set_sidecar_for_file(self, filename, sidecar_data, sidecar_sig):
|
||||||
""" sets the sidecar data and signature for a file """
|
"""sets the sidecar data and signature for a file"""
|
||||||
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
filename = str(pathlib.Path(filename).relative_to(self._path)).lower()
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
try:
|
||||||
@@ -443,7 +457,7 @@ class ExportDB(ExportDB_ABC):
|
|||||||
logging.warning(e)
|
logging.warning(e)
|
||||||
|
|
||||||
def get_previous_uuids(self):
|
def get_previous_uuids(self):
|
||||||
"""returns list of UUIDs of previously exported photos found in export database """
|
"""returns list of UUIDs of previously exported photos found in export database"""
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
previous_uuids = []
|
previous_uuids = []
|
||||||
try:
|
try:
|
||||||
@@ -455,6 +469,36 @@ class ExportDB(ExportDB_ABC):
|
|||||||
logging.warning(e)
|
logging.warning(e)
|
||||||
return previous_uuids
|
return previous_uuids
|
||||||
|
|
||||||
|
def get_detected_text_for_uuid(self, uuid):
|
||||||
|
"""Get the detected_text for a uuid"""
|
||||||
|
conn = self._conn
|
||||||
|
try:
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute(
|
||||||
|
"SELECT text_data FROM detected_text WHERE uuid = ?",
|
||||||
|
(uuid,),
|
||||||
|
)
|
||||||
|
results = c.fetchone()
|
||||||
|
detected_text = results[0] if results else None
|
||||||
|
except Error as e:
|
||||||
|
logging.warning(e)
|
||||||
|
detected_text = None
|
||||||
|
|
||||||
|
return detected_text
|
||||||
|
|
||||||
|
def set_detected_text_for_uuid(self, uuid, text_json):
|
||||||
|
"""Set the detected text for uuid"""
|
||||||
|
conn = self._conn
|
||||||
|
try:
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute(
|
||||||
|
"INSERT OR REPLACE INTO detected_text(uuid, text_data) VALUES (?, ?);",
|
||||||
|
(uuid, text_json),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
except Error as e:
|
||||||
|
logging.warning(e)
|
||||||
|
|
||||||
def set_data(
|
def set_data(
|
||||||
self,
|
self,
|
||||||
filename,
|
filename,
|
||||||
@@ -466,8 +510,7 @@ class ExportDB(ExportDB_ABC):
|
|||||||
info_json,
|
info_json,
|
||||||
exif_json,
|
exif_json,
|
||||||
):
|
):
|
||||||
""" sets all the data for file and uuid at once
|
"""sets all the data for file and uuid at once"""
|
||||||
"""
|
|
||||||
filename = str(pathlib.Path(filename).relative_to(self._path))
|
filename = str(pathlib.Path(filename).relative_to(self._path))
|
||||||
filename_normalized = filename.lower()
|
filename_normalized = filename.lower()
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
@@ -510,7 +553,7 @@ class ExportDB(ExportDB_ABC):
|
|||||||
logging.warning(e)
|
logging.warning(e)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
""" close the database connection """
|
"""close the database connection"""
|
||||||
try:
|
try:
|
||||||
self._conn.close()
|
self._conn.close()
|
||||||
except Error as e:
|
except Error as e:
|
||||||
@@ -548,9 +591,9 @@ class ExportDB(ExportDB_ABC):
|
|||||||
return stats
|
return stats
|
||||||
|
|
||||||
def _open_export_db(self, dbfile):
|
def _open_export_db(self, dbfile):
|
||||||
""" open export database and return a db connection
|
"""open export database and return a db connection
|
||||||
if dbfile does not exist, will create and initialize the database
|
if dbfile does not exist, will create and initialize the database
|
||||||
returns: connection to the database
|
returns: connection to the database
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not os.path.isfile(dbfile):
|
if not os.path.isfile(dbfile):
|
||||||
@@ -573,7 +616,7 @@ class ExportDB(ExportDB_ABC):
|
|||||||
return conn
|
return conn
|
||||||
|
|
||||||
def _get_db_connection(self, dbfile):
|
def _get_db_connection(self, dbfile):
|
||||||
""" return db connection to dbname """
|
"""return db connection to dbname"""
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(dbfile)
|
conn = sqlite3.connect(dbfile)
|
||||||
except Error as e:
|
except Error as e:
|
||||||
@@ -583,15 +626,15 @@ class ExportDB(ExportDB_ABC):
|
|||||||
return conn
|
return conn
|
||||||
|
|
||||||
def _get_database_version(self, conn):
|
def _get_database_version(self, conn):
|
||||||
""" return tuple of (osxphotos, exportdb) versions for database connection conn """
|
"""return tuple of (osxphotos, exportdb) versions for database connection conn"""
|
||||||
version_info = conn.execute(
|
version_info = conn.execute(
|
||||||
"SELECT osxphotos, exportdb, max(id) FROM version"
|
"SELECT osxphotos, exportdb, max(id) FROM version"
|
||||||
).fetchone()
|
).fetchone()
|
||||||
return (version_info[0], version_info[1])
|
return (version_info[0], version_info[1])
|
||||||
|
|
||||||
def _create_db_tables(self, conn):
|
def _create_db_tables(self, conn):
|
||||||
""" create (if not already created) the necessary db tables for the export database
|
"""create (if not already created) the necessary db tables for the export database
|
||||||
conn: sqlite3 db connection
|
conn: sqlite3 db connection
|
||||||
"""
|
"""
|
||||||
sql_commands = {
|
sql_commands = {
|
||||||
"sql_version_table": """ CREATE TABLE IF NOT EXISTS version (
|
"sql_version_table": """ CREATE TABLE IF NOT EXISTS version (
|
||||||
@@ -599,6 +642,10 @@ class ExportDB(ExportDB_ABC):
|
|||||||
osxphotos TEXT,
|
osxphotos TEXT,
|
||||||
exportdb TEXT
|
exportdb TEXT
|
||||||
); """,
|
); """,
|
||||||
|
"sql_about_table": """ CREATE TABLE IF NOT EXISTS about (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
about TEXT
|
||||||
|
);""",
|
||||||
"sql_files_table": """ CREATE TABLE IF NOT EXISTS files (
|
"sql_files_table": """ CREATE TABLE IF NOT EXISTS files (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
filepath TEXT NOT NULL,
|
filepath TEXT NOT NULL,
|
||||||
@@ -651,12 +698,18 @@ class ExportDB(ExportDB_ABC):
|
|||||||
size INTEGER,
|
size INTEGER,
|
||||||
mtime REAL
|
mtime REAL
|
||||||
); """,
|
); """,
|
||||||
|
"sql_detected_text_table": """ CREATE TABLE IF NOT EXISTS detected_text (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
uuid TEXT NOT NULL,
|
||||||
|
text_data JSON
|
||||||
|
); """,
|
||||||
"sql_files_idx": """ CREATE UNIQUE INDEX IF NOT EXISTS idx_files_filepath_normalized on files (filepath_normalized); """,
|
"sql_files_idx": """ CREATE UNIQUE INDEX IF NOT EXISTS idx_files_filepath_normalized on files (filepath_normalized); """,
|
||||||
"sql_info_idx": """ CREATE UNIQUE INDEX IF NOT EXISTS idx_info_uuid on info (uuid); """,
|
"sql_info_idx": """ CREATE UNIQUE INDEX IF NOT EXISTS idx_info_uuid on info (uuid); """,
|
||||||
"sql_exifdata_idx": """ CREATE UNIQUE INDEX IF NOT EXISTS idx_exifdata_filename on exifdata (filepath_normalized); """,
|
"sql_exifdata_idx": """ CREATE UNIQUE INDEX IF NOT EXISTS idx_exifdata_filename on exifdata (filepath_normalized); """,
|
||||||
"sql_edited_idx": """ CREATE UNIQUE INDEX IF NOT EXISTS idx_edited_filename on edited (filepath_normalized);""",
|
"sql_edited_idx": """ CREATE UNIQUE INDEX IF NOT EXISTS idx_edited_filename on edited (filepath_normalized);""",
|
||||||
"sql_converted_idx": """ CREATE UNIQUE INDEX IF NOT EXISTS idx_converted_filename on converted (filepath_normalized);""",
|
"sql_converted_idx": """ CREATE UNIQUE INDEX IF NOT EXISTS idx_converted_filename on converted (filepath_normalized);""",
|
||||||
"sql_sidecar_idx": """ CREATE UNIQUE INDEX IF NOT EXISTS idx_sidecar_filename on sidecar (filepath_normalized);""",
|
"sql_sidecar_idx": """ CREATE UNIQUE INDEX IF NOT EXISTS idx_sidecar_filename on sidecar (filepath_normalized);""",
|
||||||
|
"sql_detected_text_idx": """ CREATE UNIQUE INDEX IF NOT EXISTS idx_detected_text on detected_text (uuid);""",
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
@@ -666,12 +719,13 @@ class ExportDB(ExportDB_ABC):
|
|||||||
"INSERT INTO version(osxphotos, exportdb) VALUES (?, ?);",
|
"INSERT INTO version(osxphotos, exportdb) VALUES (?, ?);",
|
||||||
(__version__, OSXPHOTOS_EXPORTDB_VERSION),
|
(__version__, OSXPHOTOS_EXPORTDB_VERSION),
|
||||||
)
|
)
|
||||||
|
c.execute("INSERT INTO about(about) VALUES (?);", (OSXPHOTOS_ABOUT_STRING,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
except Error as e:
|
except Error as e:
|
||||||
logging.warning(e)
|
logging.warning(e)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
""" ensure the database connection is closed """
|
"""ensure the database connection is closed"""
|
||||||
try:
|
try:
|
||||||
self._conn.close()
|
self._conn.close()
|
||||||
except:
|
except:
|
||||||
@@ -696,35 +750,33 @@ class ExportDB(ExportDB_ABC):
|
|||||||
|
|
||||||
|
|
||||||
class ExportDBInMemory(ExportDB):
|
class ExportDBInMemory(ExportDB):
|
||||||
""" In memory version of ExportDB
|
"""In memory version of ExportDB
|
||||||
Copies the on-disk database into memory so it may be operated on without
|
Copies the on-disk database into memory so it may be operated on without
|
||||||
modifying the on-disk verison
|
modifying the on-disk version
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def init(self, dbfile):
|
def __init__(self, dbfile):
|
||||||
self._dbfile = dbfile
|
self._dbfile = dbfile or f"./{OSXPHOTOS_EXPORT_DB}"
|
||||||
# _path is parent of the database
|
# _path is parent of the database
|
||||||
# all files referenced by get_/set_uuid_for_file will be converted to
|
# all files referenced by get_/set_uuid_for_file will be converted to
|
||||||
# relative paths to this parent _path
|
# relative paths to this parent _path
|
||||||
# this allows the entire export tree to be moved to a new disk/location
|
# this allows the entire export tree to be moved to a new disk/location
|
||||||
# whilst preserving the UUID to filename mappping
|
# whilst preserving the UUID to filename mapping
|
||||||
self._path = pathlib.Path(dbfile).parent
|
self._path = pathlib.Path(self._dbfile).parent
|
||||||
self._conn = self._open_export_db(dbfile)
|
self._conn = self._open_export_db(self._dbfile)
|
||||||
self._insert_run_info()
|
self._insert_run_info()
|
||||||
|
|
||||||
def _open_export_db(self, dbfile):
|
def _open_export_db(self, dbfile):
|
||||||
""" open export database and return a db connection
|
"""open export database and return a db connection
|
||||||
returns: connection to the database
|
returns: connection to the database
|
||||||
"""
|
"""
|
||||||
if not os.path.isfile(dbfile):
|
if not os.path.isfile(dbfile):
|
||||||
conn = self._get_db_connection()
|
conn = self._get_db_connection()
|
||||||
if conn:
|
if not conn:
|
||||||
self._create_db_tables(conn)
|
|
||||||
self.was_created = True
|
|
||||||
self.was_upgraded = ()
|
|
||||||
self.version = OSXPHOTOS_EXPORTDB_VERSION
|
|
||||||
else:
|
|
||||||
raise Exception("Error getting connection to in-memory database")
|
raise Exception("Error getting connection to in-memory database")
|
||||||
|
self._create_db_tables(conn)
|
||||||
|
self.was_created = True
|
||||||
|
self.was_upgraded = ()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(dbfile)
|
conn = sqlite3.connect(dbfile)
|
||||||
@@ -749,12 +801,11 @@ class ExportDBInMemory(ExportDB):
|
|||||||
self.was_upgraded = (exportdb_ver, OSXPHOTOS_EXPORTDB_VERSION)
|
self.was_upgraded = (exportdb_ver, OSXPHOTOS_EXPORTDB_VERSION)
|
||||||
else:
|
else:
|
||||||
self.was_upgraded = ()
|
self.was_upgraded = ()
|
||||||
self.version = OSXPHOTOS_EXPORTDB_VERSION
|
self.version = OSXPHOTOS_EXPORTDB_VERSION
|
||||||
|
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
def _get_db_connection(self):
|
def _get_db_connection(self):
|
||||||
""" return db connection to in memory database """
|
"""return db connection to in memory database"""
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(":memory:")
|
conn = sqlite3.connect(":memory:")
|
||||||
except Error as e:
|
except Error as e:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
""" Custom template system for osxphotos, implements osxphotos template language (OTL) """
|
""" Custom template system for osxphotos, implements osxphotos template language (OTL) """
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
import locale
|
import locale
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
@@ -11,11 +12,13 @@ from typing import Optional
|
|||||||
|
|
||||||
from textx import TextXSyntaxError, metamodel_from_file
|
from textx import TextXSyntaxError, metamodel_from_file
|
||||||
|
|
||||||
from ._constants import _UNKNOWN_PERSON
|
from ._constants import _UNKNOWN_PERSON, TEXT_DETECTION_CONFIDENCE_THRESHOLD
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
from .datetime_formatter import DateTimeFormatter
|
from .datetime_formatter import DateTimeFormatter
|
||||||
from .exiftool import ExifToolCaching
|
from .exiftool import ExifToolCaching
|
||||||
|
from .export_db import ExportDB_ABC, ExportDBInMemory
|
||||||
from .path_utils import sanitize_dirname, sanitize_filename, sanitize_pathpart
|
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
|
||||||
|
|
||||||
# TODO: a lot of values are passed from function to function like path_sep--make these all class properties
|
# TODO: a lot of values are passed from function to function like path_sep--make these all class properties
|
||||||
@@ -197,6 +200,12 @@ TEMPLATE_SUBSTITUTIONS_MULTI_VALUED = {
|
|||||||
+ "For example: '{photo.favorite}' is the same as '{favorite}' and '{photo.place.name}' is the same as '{place.name}'. "
|
+ "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 "
|
+ "'{photo}' provides access to properties that are not available as separate template fields but it assumes some knowledge of "
|
||||||
+ "the underlying PhotoInfo class. See https://rhettbull.github.io/osxphotos/ for additional documentation on the PhotoInfo class.",
|
+ "the underlying PhotoInfo class. See https://rhettbull.github.io/osxphotos/ for additional documentation on the PhotoInfo class.",
|
||||||
|
"{detected_text}": "List of text strings found in the image after performing text detection. "
|
||||||
|
+ "Using '{detected_text}' will cause osxphotos to perform text detection 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}'; "
|
||||||
|
+ f"The default confidence threshold is {TEXT_DETECTION_CONFIDENCE_THRESHOLD}. "
|
||||||
|
+ "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.",
|
"{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.",
|
||||||
"{function}": "Execute a python function from an external file and use return value as template substitution. "
|
"{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. "
|
+ "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. "
|
||||||
@@ -276,6 +285,7 @@ class RenderOptions:
|
|||||||
dest_path: set to the destination path of the photo (for use by {function} template), only valid with --filename
|
dest_path: set to the destination path of the photo (for use by {function} template), only valid with --filename
|
||||||
filepath: set to value for filepath of the exported photo if you want to evaluate {filepath} template
|
filepath: set to value for filepath of the exported photo if you want to evaluate {filepath} template
|
||||||
quote: quote path templates for execution in the shell
|
quote: quote path templates for execution in the shell
|
||||||
|
exportdb: ExportDB object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
none_str: str = "_"
|
none_str: str = "_"
|
||||||
@@ -290,6 +300,7 @@ class RenderOptions:
|
|||||||
dest_path: Optional[str] = None
|
dest_path: Optional[str] = None
|
||||||
filepath: Optional[str] = None
|
filepath: Optional[str] = None
|
||||||
quote: bool = False
|
quote: bool = False
|
||||||
|
exportdb: Optional[ExportDB_ABC] = None
|
||||||
|
|
||||||
|
|
||||||
class PhotoTemplateParser:
|
class PhotoTemplateParser:
|
||||||
@@ -358,6 +369,7 @@ class PhotoTemplate:
|
|||||||
self.filepath = options.filepath
|
self.filepath = options.filepath
|
||||||
self.quote = options.quote
|
self.quote = options.quote
|
||||||
self.dest_path = options.dest_path
|
self.dest_path = options.dest_path
|
||||||
|
self.exportdb = options.exportdb or ExportDBInMemory(None)
|
||||||
|
|
||||||
def render(
|
def render(
|
||||||
self,
|
self,
|
||||||
@@ -391,6 +403,7 @@ class PhotoTemplate:
|
|||||||
self.filepath = options.filepath
|
self.filepath = options.filepath
|
||||||
self.quote = options.quote
|
self.quote = options.quote
|
||||||
self.dest_path = options.dest_path
|
self.dest_path = options.dest_path
|
||||||
|
self.exportdb = options.exportdb or self.exportdb
|
||||||
|
|
||||||
try:
|
try:
|
||||||
model = self.parser.parse(template)
|
model = self.parser.parse(template)
|
||||||
@@ -547,7 +560,7 @@ class PhotoTemplate:
|
|||||||
)
|
)
|
||||||
elif field in MULTI_VALUE_SUBSTITUTIONS or field.startswith("photo"):
|
elif field in MULTI_VALUE_SUBSTITUTIONS or field.startswith("photo"):
|
||||||
vals = self.get_template_value_multi(
|
vals = self.get_template_value_multi(
|
||||||
field, path_sep=path_sep, default=default
|
field, subfield, path_sep=path_sep, default=default
|
||||||
)
|
)
|
||||||
elif field.split(".")[0] in PATHLIB_SUBSTITUTIONS:
|
elif field.split(".")[0] in PATHLIB_SUBSTITUTIONS:
|
||||||
vals = self.get_template_value_pathlib(field)
|
vals = self.get_template_value_pathlib(field)
|
||||||
@@ -1073,11 +1086,12 @@ class PhotoTemplate:
|
|||||||
value = []
|
value = []
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def get_template_value_multi(self, field, path_sep, default):
|
def get_template_value_multi(self, field, subfield, path_sep, default):
|
||||||
"""lookup value for template field (multi-value template substitutions)
|
"""lookup value for template field (multi-value template substitutions)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
field: template field to find value for.
|
field: template field to find value for.
|
||||||
|
subfield: the template subfield value
|
||||||
path_sep: path separator to use for folder_album field
|
path_sep: path separator to use for folder_album field
|
||||||
default: value of default field
|
default: value of default field
|
||||||
|
|
||||||
@@ -1126,12 +1140,10 @@ class PhotoTemplate:
|
|||||||
folder = path_sep.join(album.folder_names)
|
folder = path_sep.join(album.folder_names)
|
||||||
folder += path_sep + album.title
|
folder += path_sep + album.title
|
||||||
values.append(folder)
|
values.append(folder)
|
||||||
|
elif self.dirname:
|
||||||
|
values.append(sanitize_dirname(album.title))
|
||||||
else:
|
else:
|
||||||
# album not in folder
|
values.append(album.title)
|
||||||
if self.dirname:
|
|
||||||
values.append(sanitize_dirname(album.title))
|
|
||||||
else:
|
|
||||||
values.append(album.title)
|
|
||||||
elif field == "comment":
|
elif field == "comment":
|
||||||
values = [
|
values = [
|
||||||
f"{comment.user}: {comment.text}" for comment in self.photo.comments
|
f"{comment.user}: {comment.text}" for comment in self.photo.comments
|
||||||
@@ -1174,6 +1186,8 @@ class PhotoTemplate:
|
|||||||
values = [str(obj)]
|
values = [str(obj)]
|
||||||
else:
|
else:
|
||||||
values = [val for val in obj]
|
values = [val for val in obj]
|
||||||
|
elif field == "detected_text":
|
||||||
|
values = _get_detected_text(self.photo, self.exportdb, confidence=subfield)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unhandled template value: {field}")
|
raise ValueError(f"Unhandled template value: {field}")
|
||||||
|
|
||||||
@@ -1414,3 +1428,37 @@ def _get_album_by_path(photo, folder_album_path):
|
|||||||
if folder_album_path.endswith(folder):
|
if folder_album_path.endswith(folder):
|
||||||
return album_info
|
return album_info
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _get_detected_text(photo, exportdb, confidence=TEXT_DETECTION_CONFIDENCE_THRESHOLD):
|
||||||
|
"""Returns the detected text for a photo
|
||||||
|
{detected_text} uses this instead of PhotoInfo.detected_text() to cache the text for all confidence values
|
||||||
|
"""
|
||||||
|
if not photo.isphoto:
|
||||||
|
return []
|
||||||
|
|
||||||
|
confidence = (
|
||||||
|
float(confidence)
|
||||||
|
if confidence is not None
|
||||||
|
else TEXT_DETECTION_CONFIDENCE_THRESHOLD
|
||||||
|
)
|
||||||
|
|
||||||
|
detected_text = exportdb.get_detected_text_for_uuid(photo.uuid)
|
||||||
|
if detected_text:
|
||||||
|
detected_text = json.loads(detected_text)
|
||||||
|
else:
|
||||||
|
path = (
|
||||||
|
photo.path_edited
|
||||||
|
if photo.hasadjustments and photo.path_edited
|
||||||
|
else photo.path
|
||||||
|
)
|
||||||
|
path = path or photo.path_derivatives[0] if photo.path_derivatives else None
|
||||||
|
if not path:
|
||||||
|
detected_text = []
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
detected_text = detect_text(path)
|
||||||
|
except Exception as e:
|
||||||
|
detected_text = []
|
||||||
|
exportdb.set_detected_text_for_uuid(photo.uuid, json.dumps(detected_text))
|
||||||
|
return [text for text, conf in detected_text if conf >= confidence]
|
||||||
|
|||||||
@@ -262,6 +262,11 @@ TEMPLATE_VALUES_DATE_NOT_MODIFIED = {
|
|||||||
"{modified.strftime,%Y-%m-%d-%H%M%S}": "2020-02-04-190738",
|
"{modified.strftime,%Y-%m-%d-%H%M%S}": "2020-02-04-190738",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UUID_DETECTED_TEXT = "E2078879-A29C-4D6F-BACB-E3BBE6C3EB91"
|
||||||
|
TEMPLATE_VALUES_DETECTED_TEXT = {
|
||||||
|
"{detected_text}": "osxphotos",
|
||||||
|
"{;+detected_text:0.5}": "osxphotos;",
|
||||||
|
}
|
||||||
|
|
||||||
COMMENT_UUID_DICT = {
|
COMMENT_UUID_DICT = {
|
||||||
"4AD7C8EF-2991-4519-9D3A-7F44A6F031BE": [
|
"4AD7C8EF-2991-4519-9D3A-7F44A6F031BE": [
|
||||||
@@ -434,7 +439,10 @@ def test_lookup_multi(photosdb_places):
|
|||||||
if subst in ["{exiftool}", "{photo}", "{function}"]:
|
if subst in ["{exiftool}", "{photo}", "{function}"]:
|
||||||
continue
|
continue
|
||||||
lookup = template.get_template_value_multi(
|
lookup = template.get_template_value_multi(
|
||||||
lookup_str, path_sep=os.path.sep, default=[]
|
lookup_str,
|
||||||
|
path_sep=os.path.sep,
|
||||||
|
default=[],
|
||||||
|
subfield=None,
|
||||||
)
|
)
|
||||||
assert isinstance(lookup, list)
|
assert isinstance(lookup, list)
|
||||||
|
|
||||||
@@ -1161,3 +1169,11 @@ def test_album_seq(photosdb):
|
|||||||
for template, value in UUID_ALBUM_SEQ[uuid]["templates"].items():
|
for template, value in UUID_ALBUM_SEQ[uuid]["templates"].items():
|
||||||
rendered, _ = photo.render_template(template, options=options)
|
rendered, _ = photo.render_template(template, options=options)
|
||||||
assert rendered[0] == value
|
assert rendered[0] == value
|
||||||
|
|
||||||
|
|
||||||
|
def test_detected_text(photosdb):
|
||||||
|
"""Test {detected_text} template"""
|
||||||
|
photo = photosdb.get_photo(UUID_DETECTED_TEXT)
|
||||||
|
for template, value in TEMPLATE_VALUES_DETECTED_TEXT.items():
|
||||||
|
rendered, _ = photo.render_template(template)
|
||||||
|
assert value in "".join(rendered)
|
||||||
|
|||||||
Reference in New Issue
Block a user