Compare commits

..

18 Commits

Author SHA1 Message Date
Rhet Turnbull
9161739ee6 Updated README.md [skip ci] 2021-05-29 09:05:05 -07:00
Rhet Turnbull
71cf8be94a Updated README.rst for PyPI 2021-05-29 09:03:35 -07:00
Rhet Turnbull
b48133cd83 Fix for #455 2021-05-29 08:51:58 -07:00
Rhet Turnbull
6b5a57fae9 Updated CHANGELOG.md [skip ci] 2021-05-28 09:09:26 -07:00
Rhet Turnbull
24ccf798c2 Updated README.md [skip ci] 2021-05-28 09:05:00 -07:00
Rhet Turnbull
a298772515 Updated tested versions to 11.3 2021-05-28 09:02:37 -07:00
Rhet Turnbull
2d68594b78 Fixes for #454 2021-05-28 08:48:21 -07:00
Rhet Turnbull
b026147c9a Updated README.md [skip ci] 2021-05-23 14:26:01 -07:00
Rhet Turnbull
186a5b77d0 Fixed bug in imageconverter exception handling, closes #440 2021-05-23 14:21:13 -07:00
Rhet Turnbull
518f855a9b PhotoInfo.exiftool now returns ExifToolCaching, closes #450 2021-05-23 14:14:22 -07:00
allcontributors[bot]
0d2067787c docs: add kaduskj as a contributor (#453)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-05-23 12:33:08 -07:00
Rhet Turnbull
0448a42329 Updated CHANGELOG.md [skip ci] 2021-05-23 12:30:10 -07:00
Rhet Turnbull
a724e15dd6 Updated README.md [skip ci] 2021-05-23 12:26:51 -07:00
Rhet Turnbull
be8fe9d059 Bug fix for #452 2021-05-23 12:01:36 -07:00
Rhet Turnbull
bd6656107b Updated CHANGELOG.md [skip ci] 2021-05-23 09:46:29 -07:00
Rhet Turnbull
a54e051d41 Updated README.md 2021-05-23 09:37:25 -07:00
Rhet Turnbull
7cde52bf9b Fixed #451, path_derivatives for Photos version <= 4 2021-05-23 09:34:40 -07:00
Rhet Turnbull
96037508c1 README.md update [skip ci] 2021-05-22 10:31:52 -07:00
16 changed files with 332 additions and 403 deletions

View File

@@ -213,6 +213,15 @@
"example",
"ideas"
]
},
{
"login": "kaduskj",
"name": "kaduskj",
"avatar_url": "https://avatars.githubusercontent.com/u/983067?v=4",
"profile": "https://github.com/kaduskj",
"contributions": [
"bug"
]
}
],
"contributorsPerLine": 7,

File diff suppressed because it is too large Load Diff

256
README.md
View File

@@ -4,7 +4,7 @@
[![tests](https://github.com/RhetTbull/osxphotos/workflows/Tests/badge.svg)](https://github.com/RhetTbull/osxphotos/workflows/Tests/badge.svg)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/osxphotos)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-22-orange.svg?style=flat)](#contributors)
[![All Contributors](https://img.shields.io/badge/all_contributors-23-orange.svg?style=flat)](#contributors)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
OSXPhotos provides the ability to interact with and query Apple's Photos.app library on macOS. You can query the Photos library database — for example, file name, file path, and metadata such as keywords/tags, persons/faces, albums, etc. You can also easily export both the original and edited photos.
@@ -35,6 +35,7 @@ OSXPhotos provides the ability to interact with and query Apple's Photos.app lib
+ [AdjustmentsInfo](#adjustmentsinfo)
+ [Raw Photos](#raw-photos)
+ [Template System](#template-system)
+ [ExifTool](#exiftoolExifTool)
+ [Utility Functions](#utility-functions)
* [Examples](#examples)
* [Related Projects](#related-projects)
@@ -53,7 +54,7 @@ Only works on macOS (aka Mac OS X). Tested on macOS Sierra (10.12.6) until macOS
| macOS Version | macOS name | Photos.app version |
| ----------------- |------------|:-------------------|
| 10.16, 11.0-11.2 | Big Sur | 6.0 ✅ |
| 10.16, 11.0-11.3 | Big Sur | 6.0 ✅ |
| 10.15.1 - 10.15.7 | Catalina | 5.0 ✅ |
| 10.14.5, 10.14.6 | Mojave | 4.0 ✅ |
| 10.13.6 | High Sierra| 3.0 ✅ |
@@ -527,13 +528,13 @@ osxphotos is very flexible. If you merely want to backup your Photos library, t
Usage: osxphotos export [OPTIONS] [PHOTOS_LIBRARY]... DEST
Export photos from the Photos database. Export path DEST is required.
Optionally, query the Photos database using 1 or more search options; if
more than one option is provided, they are treated as "AND" (e.g. search for
photos matching all options). If no query options are provided, all photos
will be exported. By default, all versions of all photos will be exported
including edited versions, live photo movies, burst photos, and associated
raw images. See --skip-edited, --skip-live, --skip-bursts, and --skip-raw
options to modify this behavior.
Optionally, query the Photos database using 1 or more search options; if more
than one option is provided, they are treated as "AND" (e.g. search for photos
matching all options). If no query options are provided, all photos will be
exported. By default, all versions of all photos will be exported including
edited versions, live photo movies, burst photos, and associated raw images.
See --skip-edited, --skip-live, --skip-bursts, and --skip-raw options to
modify this behavior.
Options:
--db <Photos database path> Specify Photos database path. Path to Photos
@@ -544,57 +545,45 @@ Options:
use in the following order: 1. last opened
library, 2. system library, 3.
~/Pictures/Photos Library.photoslibrary
-V, --verbose Print verbose output.
--keyword KEYWORD Search for photos with keyword KEYWORD. If
more than one keyword, treated as "OR", e.g.
find photos matching any keyword
--person PERSON Search for photos with person PERSON. If more
than one person, treated as "OR", e.g. find
photos matching any person
--album ALBUM Search for photos in album ALBUM. If more than
one album, treated as "OR", e.g. find photos
matching any album
--folder FOLDER Search for photos in an album in folder
FOLDER. If more than one folder, treated as
"OR", e.g. find photos in any FOLDER. Only
searches top level folders (e.g. does not look
at subfolders)
--name FILENAME Search for photos with filename matching
FILENAME. If more than one --name options is
specified, they are treated as "OR", e.g. find
photos matching any FILENAME.
--uuid UUID Search for photos with UUID(s).
--uuid-from-file FILE Search for photos with UUID(s) loaded from
FILE. Format is a single UUID per line. Lines
preceded with # are ignored.
--title TITLE Search for TITLE in title of photo.
--no-title Search for photos with no title.
--description DESC Search for DESC in description of photo.
--no-description Search for photos with no description.
--place PLACE Search for PLACE in photo's reverse
geolocation info
--no-place Search for photos with no associated place
name info (no reverse geolocation info)
--label LABEL Search for photos with image classification
label LABEL (Photos 5 only). If more than one
label, treated as "OR", e.g. find photos
matching any label
--uti UTI Search for photos whose uniform type
identifier (UTI) matches UTI
-i, --ignore-case Case insensitive search for title,
description, place, keyword, person, or album.
--edited Search for photos that have been edited.
--external-edit Search for photos edited in external editor.
--favorite Search for photos marked favorite.
@@ -603,67 +592,51 @@ Options:
--not-hidden Search for photos not marked hidden.
--shared Search for photos in shared iCloud album
(Photos 5 only).
--not-shared Search for photos not in shared iCloud album
(Photos 5 only).
--burst Search for photos that were taken in a burst.
--not-burst Search for photos that are not part of a
burst.
--live Search for Apple live photos
--not-live Search for photos that are not Apple live
photos.
--portrait Search for Apple portrait mode photos.
--not-portrait Search for photos that are not Apple portrait
mode photos.
--screenshot Search for screenshot photos.
--not-screenshot Search for photos that are not screenshot
photos.
--slow-mo Search for slow motion videos.
--not-slow-mo Search for photos that are not slow motion
videos.
--time-lapse Search for time lapse videos.
--not-time-lapse Search for photos that are not time lapse
videos.
--hdr Search for high dynamic range (HDR) photos.
--not-hdr Search for photos that are not HDR photos.
--selfie Search for selfies (photos taken with front-
facing cameras).
--not-selfie Search for photos that are not selfies.
--panorama Search for panorama photos.
--not-panorama Search for photos that are not panoramas.
--has-raw Search for photos with both a jpeg and raw
version
--only-movies Search only for movies (default searches both
images and movies).
--only-photos Search only for photos/images (default
searches both images and movies).
--from-date DATETIME Search by item start date, e.g.
2000-01-12T12:00:00,
2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO
8601 with/without timezone).
--to-date DATETIME Search by item end date, e.g.
2000-01-12T12:00:00,
2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO
8601 with/without timezone).
--from-time TIME Search by item start time of day, e.g. 12:00,
or 12:00:00.
--to-time TIME Search by item end time of day, e.g. 12:00 or
12:00:00.
--has-comment Search for photos that have comments.
--no-comment Search for photos with no comments.
--has-likes Search for photos that have likes.
@@ -671,10 +644,8 @@ Options:
--is-reference Search for photos that were imported as
referenced files (not copied into Photos
library).
--in-album Search for photos that are in one or more
albums.
--not-in-album Search for photos that are not in any albums.
--min-size SIZE Search for photos with size >= SIZE bytes. The
size evaluated is the photo's original size
@@ -683,7 +654,6 @@ Options:
units. For example, the following are all
valid and equivalent sizes: '1048576'
'1.048576MB', '1 MiB'.
--max-size SIZE Search for photos with size <= SIZE bytes. The
size evaluated is the photo's original size
(when imported to Photos). Size may be
@@ -691,14 +661,12 @@ Options:
units. For example, the following are all
valid and equivalent sizes: '1048576'
'1.048576MB', '1 MiB'.
--regex REGEX TEMPLATE Search for photos where TEMPLATE matches
regular expression REGEX. For example, to find
photos in an album that begins with 'Beach': '
--regex "^Beach" "{album}"'. You may specify
more than one regular expression match by
repeating '--regex' with different arguments.
--query-eval CRITERIA Evaluate CRITERIA to filter photos. CRITERIA
will be evaluated in context of the following
python list comprehension: `photos = [photo
@@ -713,19 +681,14 @@ Options:
https://rhettbull.github.io/osxphotos/ for
additional documentation on the PhotoInfo
class.
--missing Export only photos missing from the Photos
library; must be used with --download-missing.
--deleted Include photos from the 'Recently Deleted'
folder.
--deleted-only Include only photos from the 'Recently
Deleted' folder.
--update Only export new or updated files. See notes
below on export and --update.
--ignore-signature When used with '--update', ignores file
signature when updating files. This is useful
if you have processed or edited exported
@@ -744,15 +707,12 @@ Options:
not; 3) if a sidecar does not exist for the
photo, a sidecar will be written whether or
not the photo file was written or updated.
--only-new If used with --update, ignores any previously
exported files, even if missing from the
export folder and only exports new files that
haven't previously been exported.
--dry-run Dry run (test) the export but don't actually
export any files; most useful with --verbose.
--export-as-hardlink Hardlink files instead of copying them. Cannot
be used with --exiftool which creates copies
of the files with embedded EXIF data. Note: on
@@ -760,58 +720,45 @@ Options:
giving many of the same advantages as
hardlinks without having to use --export-as-
hardlink.
--touch-file Sets the file's modification time to match
photo date.
--overwrite Overwrite existing files. Default behavior is
to add (1), (2), etc to filename if file
already exists. Use this with caution as it
may create name collisions on export. (e.g. if
two files happen to have the same name)
--retry RETRY Automatically retry export up to RETRY times
if an error occurs during export. This may be
useful with network drives that experience
intermittent errors.
--export-by-date Automatically create output folders to
organize photos by date created (e.g.
DEST/2019/12/20/photoname.jpg).
--skip-edited Do not export edited version of photo if an
edited version exists.
--skip-original-if-edited Do not export original if there is an edited
version (exports only the edited version).
--skip-bursts Do not export all associated burst images in
the library if a photo is a burst photo.
--skip-live Do not export the associated live video
component of a live photo.
--skip-raw Do not export associated raw images of a
RAW+JPEG pair. Note: this does not skip raw
photos if the raw photo does not have an
associated jpeg image (e.g. the raw file was
imported to Photos without a jpeg preview).
--current-name Use photo's current filename instead of
original filename for export. Note: Starting
with Photos 5, all photos are renamed upon
import. By default, photos are exported with
the the original name they had before import.
--convert-to-jpeg Convert all non-jpeg images (e.g. raw, HEIC,
PNG, etc) to JPEG upon export. Only works if
your Mac has a GPU.
--jpeg-quality FLOAT RANGE Value in range 0.0 to 1.0 to use with
--convert-to-jpeg. A value of 1.0 specifies
best quality, a value of 0.0 specifies maximum
compression. Defaults to 1.0
compression. Defaults to 1.0 [0.0<=x<=1.0]
--download-missing Attempt to download missing photos from
iCloud. The current implementation uses
Applescript to interact with Photos to export
@@ -824,7 +771,6 @@ Options:
export all burst images; only the primary
photo will be exported--associated burst
images will be skipped.
--sidecar FORMAT Create sidecar for each photo exported; valid
FORMAT values: xmp, json, exiftool; --sidecar
xmp: create XMP sidecar used by Digikam, Adobe
@@ -851,7 +797,6 @@ Options:
tags exported in the JSON and exiftool
sidecar, see '--exiftool'. See also '--ignore-
signature'.
--sidecar-drop-ext Drop the photo's extension when naming sidecar
files. By default, sidecar files are named in
format 'photo_filename.photo_ext.sidecar_ext',
@@ -863,7 +808,6 @@ Options:
of different types but the same name in the
output directory, e.g. 'IMG_1234.JPG' and
'IMG_1234.MOV'.
--exiftool Use exiftool to write metadata directly to
exported photos. To use this option, exiftool
must be installed and in the path. exiftool
@@ -885,10 +829,8 @@ Options:
QuickTime:ModifyDate (see also --ignore-date-
modified); QuickTime:GPSCoordinates;
UserData:GPSCoordinates.
--exiftool-path EXIFTOOL_PATH Optionally specify path to exiftool; if not
provided, will look for exiftool in $PATH.
--exiftool-option OPTION Optional flag/option to pass to exiftool when
using --exiftool. For example, --exiftool-
option '-m' to ignore minor warnings. Specify
@@ -898,27 +840,21 @@ Options:
full list of options. More than one option may
be specified by repeating the option, e.g.
--exiftool-option '-m' --exiftool-option '-F'.
--exiftool-merge-keywords Merge any keywords found in the original file
with keywords used for '--exiftool' and '--
sidecar'.
--exiftool-merge-persons Merge any persons found in the original file
with persons used for '--exiftool' and '--
sidecar'.
--ignore-date-modified If used with --exiftool or --sidecar, will
ignore the photo modification date and set
EXIF:ModifyDate to EXIF:DateTimeOriginal; this
is consistent with how Photos handles the
EXIF:ModifyDate tag.
--person-keyword Use person in image as keyword/tag when
exporting metadata.
--album-keyword Use album name as keyword/tag when exporting
metadata.
--keyword-template TEMPLATE For use with --exiftool, --sidecar; specify a
template string to use as keyword in the form
'{name,DEFAULT}' This is the same format as
@@ -931,7 +867,6 @@ Options:
"{folder_album}" --keyword-template
"{created.year}". See '--replace-keywords' and
Templating System below.
--replace-keywords Replace keywords with any values specified
with --keyword-template. By default,
--keyword-template will add keywords to any
@@ -940,7 +875,6 @@ Options:
from --keyword-template will replace any
existing keywords instead of adding additional
keywords.
--description-template TEMPLATE
For use with --exiftool, --sidecar; specify a
template string to use as description in the
@@ -951,7 +885,6 @@ Options:
--description-template "{descr} exported with
osxphotos on {today.date}" See Templating
System below.
--finder-tag-template TEMPLATE Set MacOS Finder tags to TEMPLATE. These tags
can be searched in the Finder or Spotlight
with 'tag:tagname' format. For example, '--
@@ -960,13 +893,11 @@ Options:
TEMPLATE values by using '--finder-tag-
template' multiple times. See also '--finder-
tag-keywords and Extended Attributes below.'.
--finder-tag-keywords Set MacOS Finder tags to keywords; any
keywords specified via '--keyword-template', '
--person-keyword', etc. will also be used as
Finder tags. See also '--finder-tag-template
and Extended Attributes below.'.
--xattr-template ATTRIBUTE TEMPLATE
Set extended attribute ATTRIBUTE to TEMPLATE
value. Valid attributes are: 'authors',
@@ -977,19 +908,16 @@ Options:
findercomment "{title}; {descr}" See Extended
Attributes below for additional details on
this option.
--directory DIRECTORY Optional template for specifying name of
output directory in the form '{name,DEFAULT}'.
See below for additional details on templating
system.
--filename FILENAME Optional template for specifying name of
output file in the form '{name,DEFAULT}'. File
extension will be added automatically--do not
include an extension in the FILENAME template.
See below for additional details on templating
system.
--jpeg-ext EXTENSION Specify file extension for JPEG files. Photos
uses .jpeg for edited images but many images
are imported with .jpg or .JPG which can
@@ -999,14 +927,12 @@ Options:
exported JPEG images. Valid values are jpeg,
jpg, JPEG, JPG; e.g. '--jpeg-ext jpg' to use
'.jpg' for all JPEGs.
--strip Optionally strip leading and trailing
whitespace from any rendered templates. For
example, if --filename template is "{title,}
{original_name}" and image has no title,
resulting file would have a leading space but
if used with --strip, this will be removed.
--edited-suffix SUFFIX Optional suffix template for naming edited
photos. Default name for edited photos is in
form 'photoname_edited.ext'. For example, with
@@ -1016,7 +942,6 @@ Options:
suffix is '_edited'. Multi-value templates
(see Templating System) are not permitted with
--edited-suffix.
--original-suffix SUFFIX Optional suffix template for naming original
photos. Default name for original photos is
in form 'filename.ext'. For example, with '--
@@ -1025,11 +950,9 @@ Options:
default suffix is '' (no suffix). Multi-value
templates (see Templating System) are not
permitted with --original-suffix.
--use-photos-export Force the use of AppleScript or PhotoKit to
export even if not missing (see also '--
download-missing' and '--use-photokit').
--use-photokit Use with '--download-missing' or '--use-
photos-export' to use direct Photos interface
instead of AppleScript to export. Highly
@@ -1037,11 +960,9 @@ Options:
iTerm2 (use with Terminal.app). This is faster
and more reliable than the default AppleScript
interface.
--report <path to export report>
Write a CSV formatted report of all files that
were exported.
--cleanup Cleanup export directory by deleting any files
which were not included in this export set.
For example, photos which had previously been
@@ -1053,7 +974,6 @@ Options:
you intend before using --cleanup. Use --dry-
run with --cleanup first if you're not
certain.
--add-exported-to-album ALBUM Add all exported photos to album ALBUM in
Photos. Album ALBUM will be created if it
doesn't exist. All exported photos will be
@@ -1063,7 +983,6 @@ Options:
feature is currently experimental. I don't
know how well it will work on large export
sets.
--add-skipped-to-album ALBUM Add all skipped photos to album ALBUM in
Photos. Album ALBUM will be created if it
doesn't exist. All skipped photos will be
@@ -1073,7 +992,6 @@ Options:
feature is currently experimental. I don't
know how well it will work on large export
sets.
--add-missing-to-album ALBUM Add all missing photos to album ALBUM in
Photos. Album ALBUM will be created if it
doesn't exist. All missing photos will be
@@ -1083,7 +1001,6 @@ Options:
feature is currently experimental. I don't
know how well it will work on large export
sets.
--exportdb EXPORTDB_FILE Specify alternate name for database file which
stores state information for export and
--update. If --exportdb is not specified,
@@ -1092,7 +1009,6 @@ Options:
directory. Must be specified as filename
only, not a path, as export database will be
saved in export directory.
--load-config <config file path>
Load options from file as written with --save-
config. This allows you to save a complex
@@ -1104,11 +1020,9 @@ Options:
line options are used in conjunction with
--load-config, they will override the
corresponding values in the config file.
--save-config <config file path>
Save options to file for use with --load-
config. File format is TOML.
--help Show this message and exit.
** Export **
@@ -1163,32 +1077,25 @@ The following attributes may be used with '--xattr-template':
authors The author, or authors, of the contents of the file. A list of
strings. (com.apple.metadata:kMDItemAuthors)
comment A comment related to the file. This differs from the Finder
comment, kMDItemFinderComment. A string.
(com.apple.metadata:kMDItemComment)
copyright The copyright owner of the file contents. A string.
(com.apple.metadata:kMDItemCopyright)
description A description of the content of the resource. The description
may include an abstract, table of contents, reference to a
graphical representation of content or a free-text account of
the content. A string. (com.apple.metadata:kMDItemDescription)
findercomment Finder comments for this file. A string.
(com.apple.metadata:kMDItemFinderComment)
headline A publishable entry providing a synopsis of the contents of the
file. A string. (com.apple.metadata:kMDItemHeadline)
keywords Keywords associated with this file. For example, “Birthday”,
“Important”, etc. This differs from Finder tags
(_kMDItemUserTags) which are keywords/tags shown in the Finder
and searchable in Spotlight using "tag:tag_name". A list of
strings. (com.apple.metadata:kMDItemKeywords)
For additional information on extended attributes see: https://developer.apple.c
om/documentation/coreservices/file_metadata/mditem/common_metadata_attribute_key
s
@@ -1412,7 +1319,6 @@ Substitution Description
{name} Current filename of the photo
{original_name} Photo's original filename when imported to
Photos
{title} Title of the photo
{descr} Description of the photo
{media_type} Special media type resolved in this
@@ -1422,48 +1328,35 @@ Substitution Description
'video' if no special type. Customize one or
more media types using format: '{media_type,vi
deo=vidéo;time_lapse=vidéo_accélérée}'
{photo_or_video} 'photo' or 'video' depending on what type the
image is. To customize, use default value as
in '{photo_or_video,photo=fotos;video=videos}'
{hdr} Photo is HDR?; True/False value, use in format
'{hdr?VALUE_IF_TRUE,VALUE_IF_FALSE}'
{edited} True if photo has been edited (has
adjustments), otherwise False; use in format
'{edited?VALUE_IF_TRUE,VALUE_IF_FALSE}'
{edited_version} True if template is being rendered for the
edited version of a photo, otherwise False.
{favorite} Photo has been marked as favorite?; True/False
value, use in format
'{favorite?VALUE_IF_TRUE,VALUE_IF_FALSE}'
{created.date} Photo's creation date in ISO format, e.g.
'2020-03-22'
{created.year} 4-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
padded)
{created.month} Month name in user's locale of the photo
creation time
{created.mon} Month abbreviation in the user's locale of the
photo creation time
{created.dd} 2-digit day of the month (zero padded) of
photo creation time
{created.dow} Day of week in user's locale of the photo
creation time
{created.doy} 3-digit day of year (e.g Julian day) of photo
creation time, starting from 1 (zero padded)
{created.hour} 2-digit hour 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
@@ -1476,51 +1369,38 @@ Substitution Description
no template will return null value. See
https://strftime.org/ for help on strftime
templates.
{modified.date} Photo's modification date in ISO format, e.g.
'2020-03-22'; uses creation date if photo is
not modified
{modified.year} 4-digit year of photo modification time; uses
creation date if photo is not modified
{modified.yy} 2-digit year of photo modification time; uses
creation date if photo is not modified
{modified.mm} 2-digit month of the photo modification time
(zero padded); uses creation date if photo is
not modified
{modified.month} Month name in user's locale of the photo
modification time; uses creation date if photo
is not modified
{modified.mon} Month abbreviation in the user's locale of the
photo modification time; uses creation date if
photo is not modified
{modified.dd} 2-digit day of the month (zero padded) of the
photo modification time; uses creation date if
photo is not modified
{modified.dow} Day of week in user's locale of the photo
modification time; uses creation date if photo
is not modified
{modified.doy} 3-digit day of year (e.g Julian day) of photo
modification time, starting from 1 (zero
padded); uses creation date if photo is not
modified
{modified.hour} 2-digit hour of the photo modification time;
uses creation date if photo is not modified
{modified.min} 2-digit minute of the photo modification time;
uses creation date if photo is not modified
{modified.sec} 2-digit second of the photo modification time;
uses creation date if photo is not modified
{modified.strftime} Apply strftime template to file modification
date/time. Should be used in form
{modified.strftime,TEMPLATE} where TEMPLATE is
@@ -1531,28 +1411,21 @@ Substitution Description
creation date if photo is not modified. See
https://strftime.org/ for help on strftime
templates.
{today.date} Current date in iso format, e.g. '2020-03-22'
{today.year} 4-digit year of current date
{today.yy} 2-digit year of current date
{today.mm} 2-digit month of the current date (zero
padded)
{today.month} Month name in user's locale of the current
date
{today.mon} Month abbreviation in the user's locale of the
current date
{today.dd} 2-digit day of the month (zero padded) of
current date
{today.dow} Day of week in user's locale of the current
date
{today.doy} 3-digit day of year (e.g Julian day) of
current date, starting from 1 (zero padded)
{today.hour} 2-digit hour of the current date
{today.min} 2-digit minute of the current date
{today.sec} 2-digit second of the current date
@@ -1565,70 +1438,51 @@ Substitution Description
no template will return null value. See
https://strftime.org/ for help on strftime
templates.
{place.name} Place name from the photo's reverse
geolocation data, as displayed in Photos
{place.country_code} The ISO country code from the photo's reverse
geolocation data
{place.name.country} Country name from the photo's reverse
geolocation data
{place.name.state_province} State or province name from the photo's
reverse geolocation data
{place.name.city} City or locality name from the photo's reverse
geolocation data
{place.name.area_of_interest} Area of interest name (e.g. landmark or public
place) from the photo's reverse geolocation
data
{place.address} Postal address from the photo's reverse
geolocation data, e.g. '2007 18th St NW,
Washington, DC 20009, United States'
{place.address.street} Street part of the postal address, e.g. '2007
18th St NW'
{place.address.city} City part of the postal address, e.g.
'Washington'
{place.address.state_province} State/province part of the postal address,
e.g. 'DC'
{place.address.postal_code} Postal code part of the postal address, e.g.
'20009'
{place.address.country} Country name of the postal address, e.g.
'United States'
{place.address.country_code} ISO country code of the postal address, e.g.
'US'
{searchinfo.season} Season of the year associated with a photo,
e.g. 'Summer'; (Photos 5+ only, applied
automatically by Photos' image categorization
algorithms).
{exif.camera_make} Camera make from original photo's EXIF
information as imported by Photos, e.g.
'Apple'
{exif.camera_model} Camera model from original photo's EXIF
information as imported by Photos, e.g.
'iPhone 6s'
{exif.lens_model} Lens model from original photo's EXIF
information as imported by Photos, e.g.
'iPhone 6s back camera 4.15mm f/2.2'
{uuid} Photo's internal universally unique identifier
(UUID) for the photo, a 36-character string
unique to the photo, e.g.
'128FB4C6-0B16-4E7D-9108-FB2E90DA1546'
{comma} A comma: ','
{semicolon} A semicolon: ';'
{questionmark} A question mark: '?'
@@ -1643,6 +1497,8 @@ Substitution Description
{lf} A line feed: '\n', alias for {newline}
{cr} A carriage return: '\r'
{crlf} a carriage return + line feed: '\r\n'
{osxphotos_version} The osxphotos version, e.g. '0.42.27'
{osxphotos_cmd_line} The full command line used to run osxphotos
The following substitutions may result in multiple values. Thus if specified for
--directory these could result in multiple copies of a photo being being
@@ -1656,7 +1512,6 @@ Substitution Description
{folder_album} Folder path + album photo is contained in. e.g.
'Folder/Subfolder/Album' or just 'Album' if no
enclosing folder
{keyword} Keyword(s) assigned to photo
{person} Person(s) / face(s) in a photo
{label} Image categorization label associated with a photo
@@ -1665,11 +1520,9 @@ Substitution Description
categorize images. These are not the same as
{keyword} which refers to the user-defined
keywords/tags applied in Photos.
{label_normalized} All lower case version of 'label' (Photos 5+ only)
{comment} Comment(s) on shared Photos; format is 'Person name:
comment text' (Photos 5+ only)
{exiftool} Format: '{exiftool:GROUP:TAGNAME}'; use exiftool
(https://exiftool.org) to extract metadata, in form
GROUP:TAGNAME, from image. E.g.
@@ -1679,24 +1532,19 @@ Substitution Description
names. You must specify group (e.g. EXIF, IPTC, etc)
as used in `exiftool -G`. exiftool must be installed
in the path to use this template.
{searchinfo.holiday} Holiday names associated with a photo, e.g.
'Christmas Day'; (Photos 5+ only, applied
automatically by Photos' image categorization
algorithms).
{searchinfo.activity} Activities associated with a photo, e.g. 'Sporting
Event'; (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).
{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
@@ -1708,7 +1556,6 @@ Substitution Description
underlying PhotoInfo class. See
https://rhettbull.github.io/osxphotos/ for additional
documentation on the PhotoInfo class.
{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
@@ -1720,7 +1567,6 @@ Substitution Description
example of how to implement a template function.
```
<!-- OSXPHOTOS-EXPORT-USAGE:END -->
@@ -2502,7 +2348,7 @@ Returns an [ExifInfo](#exifinfo) object with EXIF details from the Photos databa
See also `exiftool`.
#### `exiftool`
Returns an ExifTool object for the photo which provides an interface to [exiftool](https://exiftool.org/) allowing you to read or write the actual EXIF data in the image file inside the Photos library. If [exif_info](#exif-info) doesn't give you all the data you need, you can use `exiftool` to read the entire EXIF contents of the image.
Returns an [ExifToolCaching](#exiftoolExifTool) object for the photo which provides an interface to [exiftool](https://exiftool.org/) allowing you to read the actual EXIF data in the image file inside the Photos library. If [exif_info](#exif-info) doesn't give you all the data you need, you can use `exiftool` to read the entire EXIF contents of the image.
If the file is missing from the library (e.g. not downloaded from iCloud), returns None.
@@ -2515,7 +2361,7 @@ exiftool must be installed in the path for this to work. If exiftool cannot be
>>>
```
`ExifTool` provides the following methods:
`ExifToolCaching` provides the following methods:
- `asdict(tag_groups=True)`: returns all EXIF metadata found in the file as a dictionary in following form (Note: this shows just a subset of available metadata). See [exiftool](https://exiftool.org/) documentation to understand which metadata keys are available. If `tag_groups` is True (default) dict keys are in form "GROUP:TAG", e.g. "IPTC:Keywords". If `tag_groups` is False, dict keys do not have group names, e.g. "Keywords".
@@ -2533,14 +2379,7 @@ exiftool must be installed in the path for this to work. If exiftool cannot be
- `json()`: returns same information as `asdict()` but as a serialized JSON string.
- `setvalue(tag, value)`: write to the EXIF data in the photo file. To delete a tag, use setvalue with value = `None`. For example:
```python
photo.exiftool.setvalue("XMP:Title", "Title of photo")
```
- `addvalues(tag, *values)`: Add one or more value(s) to tag. For a tag that accepts multiple values, like "IPTC:Keywords", this will add the values as additional list values. However, for tags which are not usually lists, such as "EXIF:ISO" this will literally add the new value to the old value which is probably not the desired effect. Be sure you understand the behavior of the individual tag before using this. For example:
```python
photo.exiftool.addvalues("IPTC:Keywords", "vacation", "beach")
```
The `ExifToolCaching` class caches values read from the photo via `exiftool` and is read-only. This speeds access to the underlying EXIF data but any changes made to the EXIF data in the image will not be reflected in subsequent calls to `exiftool`. In practice, the images in the Photos Library should not be modified after import so this is unlikely to cause any issues.
**Caution**: I caution against writing new EXIF data to photos in the Photos library because this will overwrite the original copy of the photo and could adversely affect how Photos behaves. `exiftool.asdict()` is useful for getting access to all the photos information but if you want to write new EXIF data, I recommend you export the photo first then write the data. [PhotoInfo.export()](#export) does this if called with `exiftool=True`.
@@ -3352,6 +3191,8 @@ The following template field substitutions are availabe for use the templating s
|{lf}|A line feed: '\n', alias for {newline}|
|{cr}|A carriage return: '\r'|
|{crlf}|a carriage return + line feed: '\r\n'|
|{osxphotos_version}|The osxphotos version, e.g. '0.42.27'|
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|{album}|Album(s) photo is contained in|
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|
|{keyword}|Keyword(s) assigned to photo|
@@ -3368,6 +3209,66 @@ The following template field substitutions are availabe for use the templating s
|{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 -->
### <a name="exiftoolExifTool">ExifTool</a>
osxphotos includes its own `exiftool` library that can be accessed via `osxphotos.exiftool`:
```python
>>> from osxphotos.exiftool import ExifTool
>>> exiftool = ExifTool("/Users/rhet/Downloads/test.jpeg")
>>> exifdict = exiftool.asdict()
>>> exifdict["EXIF:Make"]
'Canon'
>>> exiftool.setvalue("IPTC:Keywords","Keyword1")
True
>>> exiftool.asdict()["IPTC:Keywords"]
'Keyword1'
>>> exiftool.addvalues("IPTC:Keywords","Keyword2","Keyword3")
True
>>> exiftool.asdict()["IPTC:Keywords"]
['Keyword1', 'Keyword2', 'Keyword3']
```
`ExifTool(filepath, exiftool=None)`
- `filepath`: str, path to photo
- `exiftool`: str, optional path to `exiftool`; if not provided, will look for `exiftool` in the system path
#### ExifTool methods
- `asdict(tag_groups=True)`: returns all EXIF metadata found in the file as a dictionary in following form (Note: this shows just a subset of available metadata). See [exiftool](https://exiftool.org/) documentation to understand which metadata keys are available. If `tag_groups` is True (default) dict keys are in form "GROUP:TAG", e.g. "IPTC:Keywords". If `tag_groups` is False, dict keys do not have group names, e.g. "Keywords".
```python
{'Composite:Aperture': 2.2,
'Composite:GPSPosition': '-34.9188916666667 138.596861111111',
'Composite:ImageSize': '2754 2754',
'EXIF:CreateDate': '2017:06:20 17:18:56',
'EXIF:LensMake': 'Apple',
'EXIF:LensModel': 'iPhone 6s back camera 4.15mm f/2.2',
'EXIF:Make': 'Apple',
'XMP:Title': 'Elder Park',
}
```
- `json()`: returns same information as `asdict()` but as a serialized JSON string.
- `setvalue(tag, value)`: write to the EXIF data in the photo file. To delete a tag, use setvalue with value = `None`. For example:
```python
photo.exiftool.setvalue("XMP:Title", "Title of photo")
```
- `addvalues(tag, *values)`: Add one or more value(s) to tag. For a tag that accepts multiple values, like "IPTC:Keywords", this will add the values as additional list values. However, for tags which are not usually lists, such as "EXIF:ISO" this will literally add the new value to the old value which is probably not the desired effect. Be sure you understand the behavior of the individual tag before using this. For example:
```python
photo.exiftool.addvalues("IPTC:Keywords", "vacation", "beach")
```
osxphotos.exiftool also provides an `ExifToolCaching` class which caches all metadata after the first call to `exiftool`. This can significantly speed up repeated access to the metadata but should only be used if you do not intend to modify the file's metadata.
[`PhotoInfo.exiftool`](#exiftool) returns an `ExifToolCaching` instance for the original image in the Photos library.
#### Implementation Note
`ExifTool()` runs `exiftool` as a subprocess using the `-stay_open True` flag to keep the process running in the background. The subprocess will be cleaned up when your main script terminates. `ExifTool()` uses a singleton pattern to ensure that only one instance of `exiftool` is created. Multiple instances of `ExifTool()` will all use the same `exiftool` subprocess.
### Utility Functions
The following functions are located in osxphotos.utils
@@ -3504,6 +3405,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
</tr>
<tr>
<td align="center"><a href="http://blog.dewost.com/"><img src="https://avatars.githubusercontent.com/u/17090228?v=4?s=75" width="75px;" alt=""/><br /><sub><b>Philippe Dewost</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/commits?author=pdewost" title="Documentation">📖</a> <a href="#example-pdewost" title="Examples">💡</a> <a href="#ideas-pdewost" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/kaduskj"><img src="https://avatars.githubusercontent.com/u/983067?v=4?s=75" width="75px;" alt=""/><br /><sub><b>kaduskj</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Akaduskj" title="Bug reports">🐛</a></td>
</tr>
</table>

View File

@@ -16,8 +16,7 @@ You can also easily export both the original and edited photos.
Supported operating systems
---------------------------
Only works on macOS (aka Mac OS X). Tested on macOS Sierra (10.12.6) until macOS Catalina (10.15.7).
Beta support for macOS Big Sur (10.16.01/11.01).
Only works on macOS (aka Mac OS X). Tested on macOS Sierra (10.12.6) through macOS Big Sur (11.3).
This package will read Photos databases for any supported version on any supported macOS version.
E.g. you can read a database created with Photos 5.0 on MacOS 10.15 on a machine running macOS 10.12 and vice versa.

View File

@@ -70,6 +70,7 @@ _TESTED_OS_VERSIONS = [
("11", "0"),
("11", "1"),
("11", "2"),
("11", "3"),
]
# Photos 5 has persons who are empty string if unidentified face
@@ -212,8 +213,13 @@ EXTENDED_ATTRIBUTE_NAMES_QUOTED = [f"'{x}'" for x in EXTENDED_ATTRIBUTE_NAMES]
OSXPHOTOS_EXPORT_DB = ".osxphotos_export.db"
# bit flags for burst images ("burstPickType")
BURST_NOT_SELECTED = 0b10 # 2: burst image is not selected
BURST_DEFAULT_PICK = 0b100 # 4: burst image is the one Photos picked to be key image before any selections made
BURST_SELECTED = 0b1000 # 8: burst image is selected
BURST_KEY = 0b10000 # 16: burst image is the key photo (top of burst stack)
BURST_UNKNOWN = 0b100000 # 32: this is almost always set with BURST_DEFAULT_PICK and never if BURST_DEFAULT_PICK is not set. I think this has something to do with what algorithm Photos used to pick the default image
BURST_NOT_SELECTED = 0b10 # 2: burst image is not selected
BURST_DEFAULT_PICK = (
0b100
) # 4: burst image is the one Photos picked to be key image before any selections made
BURST_SELECTED = 0b1000 # 8: burst image is selected
BURST_KEY = 0b10000 # 16: burst image is the key photo (top of burst stack)
BURST_UNKNOWN = (
0b100000
) # 32: this is almost always set with BURST_DEFAULT_PICK and never if BURST_DEFAULT_PICK is not set. I think this has something to do with what algorithm Photos used to pick the default image

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.42.22"
__version__ = "0.42.27"

View File

@@ -23,12 +23,14 @@ EXIFTOOL_STAYOPEN_EOF_LEN = len(EXIFTOOL_STAYOPEN_EOF)
# list of exiftool processes to cleanup when exiting or when terminate is called
EXIFTOOL_PROCESSES = []
@atexit.register
def terminate_exiftool():
"""Terminate any running ExifTool subprocesses; call this to cleanup when done using ExifTool """
for proc in EXIFTOOL_PROCESSES:
proc._stop_proc()
@lru_cache(maxsize=1)
def get_exiftool_path():
""" return path of exiftool, cache result """
@@ -70,15 +72,14 @@ class _ExifToolProc:
self._exiftool = exiftool or get_exiftool_path()
self._start_proc()
EXIFTOOL_PROCESSES.append(self)
@property
def process(self):
""" return the exiftool subprocess """
if self._process_running:
return self._process
else:
raise ValueError("exiftool process is not running")
self._start_proc()
return self._process
@property
def pid(self):
@@ -116,15 +117,21 @@ class _ExifToolProc:
)
self._process_running = True
EXIFTOOL_PROCESSES.append(self)
def _stop_proc(self):
""" stop the exiftool process if it's running, otherwise, do nothing """
if not self._process_running:
return
self._process.stdin.write(b"-stay_open\n")
self._process.stdin.write(b"False\n")
self._process.stdin.flush()
try:
self._process.stdin.write(b"-stay_open\n")
self._process.stdin.write(b"False\n")
self._process.stdin.flush()
except Exception as e:
pass
try:
self._process.communicate(timeout=5)
except subprocess.TimeoutExpired:
@@ -134,9 +141,6 @@ class _ExifToolProc:
del self._process
self._process_running = False
def __del__(self):
self._stop_proc()
class ExifTool:
""" Basic exiftool interface for reading and writing EXIF tags """
@@ -162,9 +166,12 @@ class ExifTool:
# if running as a context manager, self._context_mgr will be True
self._context_mgr = False
self._exiftoolproc = _ExifToolProc(exiftool=exiftool)
self._process = self._exiftoolproc.process
self._read_exif()
@property
def _process(self):
return self._exiftoolproc.process
def setvalue(self, tag, value):
"""Set tag to value(s); if value is None, will delete tag
@@ -194,7 +201,7 @@ class ExifTool:
return True
else:
_, _, error = self.run_commands(*command)
return error is None
return error == ""
def addvalues(self, tag, *values):
"""Add one or more value(s) to tag
@@ -236,7 +243,7 @@ class ExifTool:
return True
else:
_, _, error = self.run_commands(*command)
return error is None
return error == ""
def run_commands(self, *commands, no_file=False):
"""Run commands in the exiftool process and return result.

View File

@@ -121,6 +121,6 @@ class ImageConverter:
return True
else:
raise ImageConversionError(
"Error converting file {input_path} to jpeg at {output_path}: {error}"
f"Error converting file {input_path} to jpeg at {output_path}: {error}"
)

View File

@@ -3,13 +3,13 @@
import logging
import os
from ..exiftool import ExifTool, get_exiftool_path
from ..exiftool import ExifToolCaching, get_exiftool_path
@property
def exiftool(self):
""" Returns an ExifTool object for the photo
requires that exiftool (https://exiftool.org/) be installed
""" Returns a ExifToolCaching (read-only instance of ExifTool) object for the photo.
Requires that exiftool (https://exiftool.org/) be installed
If exiftool not installed, logs warning and returns None
If photo path is missing, returns None
"""
@@ -20,7 +20,7 @@ def exiftool(self):
try:
exiftool_path = self._db._exiftool_path or get_exiftool_path()
if self.path is not None and os.path.isfile(self.path):
exiftool = ExifTool(self.path, exiftool=exiftool_path)
exiftool = ExifToolCaching(self.path, exiftool=exiftool_path)
else:
exiftool = None
except FileNotFoundError:

View File

@@ -838,7 +838,7 @@ class PhotoInfo:
def _path_derivatives_4(self):
""" Return paths to all derivative (preview) files for Photos <= 4"""
modelid = self._info["masterModelID"]
modelid = self._info["modelID"]
if modelid is None:
return []
folder_id, file_id = _get_resource_loc(modelid)

View File

@@ -1615,7 +1615,11 @@ class PhotosDB:
for person in c:
pk = person[0]
fullname = person[2] if person[2] != "" else _UNKNOWN_PERSON
fullname = (
person[2]
if (person[2] != "" and person[2] is not None)
else _UNKNOWN_PERSON
)
self._dbpersons_pk[pk] = {
"pk": pk,
"uuid": person[1],

View File

@@ -268,14 +268,23 @@ def list_photo_libraries():
def get_preferred_uti_extension(uti):
""" get preferred extension for a UTI type
uti: UTI str, e.g. 'public.jpeg'
returns: preferred extension as str """
returns: preferred extension as str or None if cannot be determined """
# reference: https://developer.apple.com/documentation/coreservices/1442744-uttypecopypreferredtagwithclass?language=objc
with objc.autorelease_pool():
return CoreServices.UTTypeCopyPreferredTagWithClass(
extension = CoreServices.UTTypeCopyPreferredTagWithClass(
uti, CoreServices.kUTTagClassFilenameExtension
)
if extension:
return extension
# on MacOS 10.12, HEIC files are not supported and UTTypeCopyPreferredTagWithClass will return None for HEIC
if uti == "public.heic":
return "HEIC"
return None
def findfiles(pattern, path_):
"""Returns list of filenames from path_ matched by pattern

View File

@@ -3,9 +3,10 @@ import os
import pathlib
import pytest
from applescript import AppleScript
from photoscript.utils import ditto
import osxphotos
from applescript import AppleScript
from osxphotos.exiftool import _ExifToolProc
@@ -63,7 +64,9 @@ def pytest_collection_modifyitems(config, items):
# --addalbum given in cli: do not skip addalbum tests (these require interactive test)
return
skip_addalbum = pytest.mark.skip(reason="need --addalbum option and MacOS Catalina to run")
skip_addalbum = pytest.mark.skip(
reason="need --addalbum option and MacOS Catalina to run"
)
for item in items:
if "addalbum" in item.keywords:
item.add_marker(skip_addalbum)

View File

@@ -30,6 +30,8 @@ KEYWORDS_DICT = {
PERSONS_DICT = {"Katie": 3, "Suzy": 2, "Maria": 1, _UNKNOWN_PERSON: 1}
ALBUM_DICT = {"Pumpkin Farm": 3, "AlbumInFolder": 1}
UUID_DICT = {"derivatives": "FPm+ICxpQV+LPBKR22UepA"}
def test_init():
import osxphotos
@@ -162,3 +164,19 @@ def test_keyword_not_in_album():
photos3 = [p for p in photos2 if p not in photos1]
assert len(photos3) == 1
assert photos3[0].uuid == "Pj99JmYjQkeezdY2OFuSaw"
def test_path_derivatives():
# test path_derivatives
import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photos = photosdb.photos(uuid=[UUID_DICT["derivatives"]])
p = photos[0]
derivs = [
"/resources/proxies/derivatives/00/00/9/UNADJUSTEDRAW_thumb_9.jpg",
"/resources/proxies/derivatives/00/00/9/UNADJUSTEDRAW_mini_9.jpg",
]
for i, p in enumerate(p.path_derivatives):
assert p.endswith(derivs[i])

View File

@@ -34,9 +34,9 @@ UUID_BURST_ALBUM = {
"TestBurst2/IMG_9814.JPG",
],
"38F8F30C-FF6D-49DA-8092-18497F1D6628": [
"TestBurst/IMG_9812.JPG",
"TestBurst/IMG_9812.JPG",
"TestBurst/IMG_9813.JPG",
"TestBurst/IMG_9814.JPG", # in my personal library, "38F8F30C-FF6D-49DA-8092-18497F1D6628"
"TestBurst/IMG_9814.JPG", # in my personal library, "38F8F30C-FF6D-49DA-8092-18497F1D6628"
"TestBurst/IMG_9815.JPG",
"TestBurst/IMG_9816.JPG",
"TestBurst2/IMG_9814.JPG",
@@ -1229,16 +1229,12 @@ def test_export_exiftool_path():
import shutil
import tempfile
from osxphotos.cli import export
from osxphotos.exiftool import ExifTool, get_exiftool_path
from osxphotos.exiftool import ExifTool
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
tempdir = tempfile.TemporaryDirectory()
exiftool_source = get_exiftool_path()
exiftool_path = os.path.join(tempdir.name, "myexiftool")
shutil.copy2(exiftool_source, exiftool_path)
for uuid in CLI_EXIFTOOL:
result = runner.invoke(
export,
@@ -1250,11 +1246,11 @@ def test_export_exiftool_path():
"--uuid",
f"{uuid}",
"--exiftool-path",
exiftool_path,
exiftool,
],
)
assert result.exit_code == 0
assert f"exiftool path: {exiftool_path}" in result.output
assert f"exiftool path: {exiftool}" in result.output
files = glob.glob("*")
assert sorted(files) == sorted([CLI_EXIFTOOL[uuid]["File:FileName"]])
@@ -1290,9 +1286,6 @@ def test_export_exiftool_path_render_template():
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
tempdir = tempfile.TemporaryDirectory()
exiftool_path = os.path.join(tempdir.name, "myexiftool")
shutil.copy2(exiftool_source, exiftool_path)
for uuid in CLI_EXIFTOOL:
result = runner.invoke(
export,
@@ -1305,7 +1298,7 @@ def test_export_exiftool_path_render_template():
"--uuid",
f"{uuid}",
"--exiftool-path",
exiftool_path,
exiftool,
],
)
assert result.exit_code == 0

View File

@@ -425,8 +425,17 @@ def test_exiftool_terminate():
import subprocess
exif1 = osxphotos.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD)
ps = subprocess.run(["ps"], capture_output=True)
stdout = ps.stdout.decode("utf-8")
assert "exiftool -stay_open" in stdout
osxphotos.exiftool.terminate_exiftool()
ps = subprocess.run(["ps"], capture_output=True)
stdout = ps.stdout.decode("utf-8")
assert "exiftool -stay_open" not in stdout
# verify we can create a new instance after termination
exif2 = osxphotos.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD)
assert exif2.asdict()["IPTC:Keywords"] == "wedding"