Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24ccf798c2 | ||
|
|
a298772515 | ||
|
|
2d68594b78 | ||
|
|
b026147c9a | ||
|
|
186a5b77d0 | ||
|
|
518f855a9b | ||
|
|
0d2067787c | ||
|
|
0448a42329 | ||
|
|
a724e15dd6 | ||
|
|
be8fe9d059 | ||
|
|
bd6656107b | ||
|
|
a54e051d41 | ||
|
|
7cde52bf9b | ||
|
|
96037508c1 | ||
|
|
9f2268fb2b | ||
|
|
df167c00eb | ||
|
|
e8f9cda0c6 | ||
|
|
d4a951f547 | ||
|
|
f24e4a7e3c | ||
|
|
98b84c17f1 | ||
|
|
78c411a643 | ||
|
|
6bdf15b41e | ||
|
|
a0fcec2a7a | ||
|
|
63834ab8ab | ||
|
|
b23cfa32bb | ||
|
|
0e22ce54ab | ||
|
|
0f41588701 | ||
|
|
442b542794 | ||
|
|
88fae81b19 |
@@ -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,
|
||||
|
||||
346
CHANGELOG.md
346
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
282
README.md
282
README.md
@@ -4,7 +4,7 @@
|
||||
[](https://github.com/RhetTbull/osxphotos/workflows/Tests/badge.svg)
|
||||

|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors)
|
||||
[](#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 ✅ |
|
||||
@@ -163,6 +164,10 @@ To get help on a specific command, use `osxphotos help <command_name>`
|
||||
|
||||
`osxphotos query --keyword Kids --json ~/Pictures/Photos\ Library.photoslibrary >results.json`
|
||||
|
||||
#### Find all videos larger than 200MB and add them to an album named "Big Videos" in Photos, creating the album if necessary
|
||||
|
||||
`osxphotos query --only-movies --min-size 200MB --add-to-album "Big Videos"`
|
||||
|
||||
### Tutorial
|
||||
<!-- OSXPHOTOS-TUTORIAL:START --><!-- OSXPHOTOS-TUTORIAL-HEADER:START --><!-- OSXPHOTOS-TUTORIAL-HEADER:END -->
|
||||
|
||||
@@ -523,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
|
||||
@@ -540,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.
|
||||
@@ -599,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.
|
||||
@@ -667,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
|
||||
@@ -679,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
|
||||
@@ -687,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
|
||||
@@ -709,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
|
||||
@@ -740,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
|
||||
@@ -756,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
|
||||
@@ -820,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
|
||||
@@ -847,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',
|
||||
@@ -859,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
|
||||
@@ -881,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
|
||||
@@ -894,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
|
||||
@@ -927,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
|
||||
@@ -936,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
|
||||
@@ -947,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, '--
|
||||
@@ -956,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',
|
||||
@@ -973,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
|
||||
@@ -995,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
|
||||
@@ -1012,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 '--
|
||||
@@ -1021,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
|
||||
@@ -1033,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
|
||||
@@ -1049,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
|
||||
@@ -1059,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
|
||||
@@ -1069,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
|
||||
@@ -1079,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,
|
||||
@@ -1088,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
|
||||
@@ -1100,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 **
|
||||
@@ -1159,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
|
||||
@@ -1408,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
|
||||
@@ -1418,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
|
||||
@@ -1472,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
|
||||
@@ -1527,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
|
||||
@@ -1561,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: '?'
|
||||
@@ -1639,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.26'
|
||||
{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
|
||||
@@ -1652,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
|
||||
@@ -1661,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.
|
||||
@@ -1675,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
|
||||
@@ -1704,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
|
||||
@@ -1716,7 +1567,6 @@ Substitution Description
|
||||
example of how to implement a template function.
|
||||
|
||||
|
||||
|
||||
```
|
||||
<!-- OSXPHOTOS-EXPORT-USAGE:END -->
|
||||
|
||||
@@ -2217,6 +2067,9 @@ Returns the original filename of the photo when it was imported to Photos. **No
|
||||
#### `date`
|
||||
Returns the create date of the photo as a datetime.datetime object
|
||||
|
||||
#### `date_added`
|
||||
Returns the date the photo was added to the Photos library as a timezone aware datetime.datetime object, or None if the data added cannot be determined
|
||||
|
||||
#### `date_modified`
|
||||
Returns the modification date of the photo as a datetime.datetime object or None if photo has no modification date
|
||||
|
||||
@@ -2255,6 +2108,23 @@ Returns the absolute path to the edited photo on disk as a string. If the photo
|
||||
|
||||
**Note**: will also return None if the edited photo is missing on disk.
|
||||
|
||||
#### `path_derivatives`
|
||||
Returns list of paths to any derivative preview images associated with the photo. The list of returned paths is sorted in descieding order by size (the largest, presumably highest quality) preview image will be the first element in the returned list. These will be named something like this on Photos 5+:
|
||||
|
||||
- `F19E06B8-A712-4B5C-907A-C007D37BDA16_1_101_o.jpeg`
|
||||
- `F19E06B8-A712-4B5C-907A-C007D37BDA16_1_102_o.jpeg`
|
||||
- `F19E06B8-A712-4B5C-907A-C007D37BDA16_1_105_c.jpeg`
|
||||
|
||||
On Photos <=4, they'll be named something like:
|
||||
|
||||
- `UNADJUSTEDNONRAW_mini_6.jpg`
|
||||
- `UNADJUSTEDNONRAW_thumb_6.jpg`
|
||||
- `Y6OofYkbR96spbS6XgwOQw_mini_1.jpg`
|
||||
|
||||
I've not yet decoded the suffixes to know which preview is used for which purpose but in general, if you look for the largest file, you'll get the highest resolution preview. Note that video files and Live images may have both a `.mov` video preview as well as a `.jpeg` still-image preview (the JPEG file is the one Photos displays as the "cover" for the video.)
|
||||
|
||||
Returns empty list if no preview images are found.
|
||||
|
||||
#### `path_raw`
|
||||
Returns the absolute path to the associated raw photo on disk as a string, if photo is part of a RAW+JPEG pair, otherwise returns None. See [notes on Raw Photos](#raw-photos).
|
||||
|
||||
@@ -2478,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.
|
||||
|
||||
@@ -2491,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".
|
||||
|
||||
@@ -2509,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`.
|
||||
|
||||
@@ -3328,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.26'|
|
||||
|{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|
|
||||
@@ -3344,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
|
||||
@@ -3480,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>
|
||||
|
||||
@@ -3501,7 +3427,7 @@ My goal is make osxphotos as reliable and comprehensive as possible. The test s
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This package works by creating a copy of the sqlite3 database that photos uses to store data about the photos library. The class PhotosDB then queries this database to extract information about the photos such as persons (faces identified in the photos), albums, keywords, etc. If your library is large, the database can be hundreds of MB in size and the copy read then can take many 10s of seconds to complete. Once copied, the entire database is processed and an in-memory data structure is created meaning all subsequent accesses of the PhotosDB object occur much more quickly.
|
||||
This package works by creating a copy of the sqlite3 database that photos uses to store data about the photos library. The class PhotosDB then queries this database to extract information about the photos such as persons (faces identified in the photos), albums, keywords, etc. If your library is large, the database can be hundreds of MB in size and the copy read then can take many 10s of seconds to complete. Once copied, the entire database is processed and an in-memory data structure is created meaning all subsequent accesses of the PhotosDB object occur much more quickly. The database processing code is rather ugly (though it works and is well tested). Were I to start this project today, I'd likely use something like SQLAlchemy to map Python objects to the underlying SQL database instead of the way osxphotos does things today.
|
||||
|
||||
If apple changes the database format this will likely break.
|
||||
|
||||
|
||||
@@ -146,6 +146,11 @@ export default library using 'country name/year' as output directory (but use "N
|
||||
|
||||
``osxphotos export ~/Desktop/export --directory "{place.name.country,NoCountry}/{created.year}" --person-keyword --album-keyword --keyword-template "{created.year}" --exiftool --update --verbose``
|
||||
|
||||
find all videos larger than 200MB and add them to Photos album "Big Videos" creating the album if necessary
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``osxphotos query --only-movies --min-size 200MB --add-to-album "Big Videos"``
|
||||
|
||||
Example uses of the package
|
||||
---------------------------
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: 03429d5418e3acb89bac82f09774e4ee
|
||||
config: 210ecd9d654dea5d4c21627449ca1d63
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Overview: module code — osxphotos 0.42.15 documentation</title>
|
||||
<title>Overview: module code — osxphotos 0.42.20 documentation</title>
|
||||
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
|
||||
<link rel="stylesheet" href="../_static/alabaster.css" type="text/css" />
|
||||
<script id="documentation_options" data-url_root="../" src="../_static/documentation_options.js"></script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos.photoinfo._photoinfo_export — osxphotos 0.41.10 documentation</title>
|
||||
<title>osxphotos.photoinfo._photoinfo_export — osxphotos 0.42.17 documentation</title>
|
||||
<link rel="stylesheet" href="../../../_static/pygments.css" type="text/css" />
|
||||
<link rel="stylesheet" href="../../../_static/alabaster.css" type="text/css" />
|
||||
<script id="documentation_options" data-url_root="../../../" src="../../../_static/documentation_options.js"></script>
|
||||
@@ -122,6 +122,9 @@
|
||||
<span class="n">xattr_skipped</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">deleted_files</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">deleted_directories</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">exported_album</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">skipped_album</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">missing_album</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">exported</span> <span class="o">=</span> <span class="n">exported</span> <span class="ow">or</span> <span class="p">[]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">new</span> <span class="o">=</span> <span class="n">new</span> <span class="ow">or</span> <span class="p">[]</span>
|
||||
@@ -144,6 +147,9 @@
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">xattr_skipped</span> <span class="o">=</span> <span class="n">xattr_skipped</span> <span class="ow">or</span> <span class="p">[]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">deleted_files</span> <span class="o">=</span> <span class="n">deleted_files</span> <span class="ow">or</span> <span class="p">[]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">deleted_directories</span> <span class="o">=</span> <span class="n">deleted_directories</span> <span class="ow">or</span> <span class="p">[]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">exported_album</span> <span class="o">=</span> <span class="n">exported_album</span> <span class="ow">or</span> <span class="p">[]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">skipped_album</span> <span class="o">=</span> <span class="n">skipped_album</span> <span class="ow">or</span> <span class="p">[]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">missing_album</span> <span class="o">=</span> <span class="n">missing_album</span> <span class="ow">or</span> <span class="p">[]</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">all_files</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">""" return all filenames contained in results """</span>
|
||||
@@ -190,6 +196,10 @@
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">exiftool_error</span> <span class="o">+=</span> <span class="n">other</span><span class="o">.</span><span class="n">exiftool_error</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">deleted_files</span> <span class="o">+=</span> <span class="n">other</span><span class="o">.</span><span class="n">deleted_files</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">deleted_directories</span> <span class="o">+=</span> <span class="n">other</span><span class="o">.</span><span class="n">deleted_directories</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">exported_album</span> <span class="o">+=</span> <span class="n">other</span><span class="o">.</span><span class="n">exported_album</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">skipped_album</span> <span class="o">+=</span> <span class="n">other</span><span class="o">.</span><span class="n">skipped_album</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">missing_album</span> <span class="o">+=</span> <span class="n">other</span><span class="o">.</span><span class="n">missing_album</span>
|
||||
|
||||
<span class="k">return</span> <span class="bp">self</span>
|
||||
|
||||
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
@@ -214,6 +224,9 @@
|
||||
<span class="o">+</span> <span class="sa">f</span><span class="s2">",exiftool_error=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">exiftool_error</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="o">+</span> <span class="sa">f</span><span class="s2">",deleted_files=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">deleted_files</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="o">+</span> <span class="sa">f</span><span class="s2">",deleted_directories=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">deleted_directories</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="o">+</span> <span class="sa">f</span><span class="s2">",exported_album=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">exported_album</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="o">+</span> <span class="sa">f</span><span class="s2">",skipped_album=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">skipped_album</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="o">+</span> <span class="sa">f</span><span class="s2">",missing_album=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">missing_album</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="o">+</span> <span class="s2">")"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
@@ -654,7 +667,11 @@
|
||||
<span class="p">)</span>
|
||||
<span class="n">edited_name</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">path_edited</span><span class="p">)</span><span class="o">.</span><span class="n">name</span>
|
||||
<span class="n">edited_suffix</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">edited_name</span><span class="p">)</span><span class="o">.</span><span class="n">suffix</span>
|
||||
<span class="n">fname</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">original_filename</span><span class="p">)</span><span class="o">.</span><span class="n">stem</span> <span class="o">+</span> <span class="n">edited_identifier</span> <span class="o">+</span> <span class="n">edited_suffix</span>
|
||||
<span class="n">fname</span> <span class="o">=</span> <span class="p">(</span>
|
||||
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">original_filename</span><span class="p">)</span><span class="o">.</span><span class="n">stem</span>
|
||||
<span class="o">+</span> <span class="n">edited_identifier</span>
|
||||
<span class="o">+</span> <span class="n">edited_suffix</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">fname</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">original_filename</span>
|
||||
|
||||
@@ -1687,7 +1704,7 @@
|
||||
<span class="n">exif</span><span class="p">[</span><span class="s2">"QuickTime:ModifyDate"</span><span class="p">]</span> <span class="o">=</span> <span class="n">datetime_tz_to_utc</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">date_modified</span>
|
||||
<span class="p">)</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">"%Y:%m:</span><span class="si">%d</span><span class="s2"> %H:%M:%S"</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="c1"># remove any new lines in any fields</span>
|
||||
<span class="k">for</span> <span class="n">field</span><span class="p">,</span> <span class="n">val</span> <span class="ow">in</span> <span class="n">exif</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="nb">type</span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="o">==</span> <span class="nb">str</span><span class="p">:</span>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos.photoinfo.photoinfo — osxphotos 0.42.12 documentation</title>
|
||||
<title>osxphotos.photoinfo.photoinfo — osxphotos 0.42.20 documentation</title>
|
||||
<link rel="stylesheet" href="../../../_static/pygments.css" type="text/css" />
|
||||
<link rel="stylesheet" href="../../../_static/alabaster.css" type="text/css" />
|
||||
<script id="documentation_options" data-url_root="../../../" src="../../../_static/documentation_options.js"></script>
|
||||
@@ -521,7 +521,7 @@
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_burst_album_info</span>
|
||||
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
|
||||
<span class="n">burst_album_info</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">album_info</span><span class="p">)</span>
|
||||
<span class="n">burst_album_info</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">album_info</span><span class="p">)</span>
|
||||
<span class="k">for</span> <span class="n">photo</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">burst_photos</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">photo</span><span class="o">.</span><span class="n">burst_key</span><span class="p">:</span>
|
||||
<span class="n">burst_album_info</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">photo</span><span class="o">.</span><span class="n">album_info</span><span class="p">)</span>
|
||||
@@ -637,6 +637,23 @@
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="kc">None</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">date_added</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">""" Date photo was added to the database """</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_date_added</span>
|
||||
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
|
||||
<span class="n">added_date</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">"added_date"</span><span class="p">]</span>
|
||||
<span class="k">if</span> <span class="n">added_date</span><span class="p">:</span>
|
||||
<span class="n">seconds</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">"imageTimeZoneOffsetSeconds"</span><span class="p">]</span> <span class="ow">or</span> <span class="mi">0</span>
|
||||
<span class="n">delta</span> <span class="o">=</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">seconds</span><span class="o">=</span><span class="n">seconds</span><span class="p">)</span>
|
||||
<span class="n">tz</span> <span class="o">=</span> <span class="n">timezone</span><span class="p">(</span><span class="n">delta</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_date_added</span> <span class="o">=</span> <span class="n">added_date</span><span class="o">.</span><span class="n">astimezone</span><span class="p">(</span><span class="n">tz</span><span class="o">=</span><span class="n">tz</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_date_added</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_date_added</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">location</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">""" returns (latitude, longitude) as float in degrees or None """</span>
|
||||
@@ -834,6 +851,60 @@
|
||||
|
||||
<span class="k">return</span> <span class="n">photopath</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">path_derivatives</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">""" Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first) """</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_version</span> <span class="o"><=</span> <span class="n">_PHOTOS_4_VERSION</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_path_derivatives_4</span><span class="p">()</span>
|
||||
|
||||
<span class="n">directory</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_uuid</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="c1"># first char of uuid</span>
|
||||
<span class="n">derivative_path</span> <span class="o">=</span> <span class="p">(</span>
|
||||
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_library_path</span><span class="p">)</span>
|
||||
<span class="o">/</span> <span class="s2">"resources"</span>
|
||||
<span class="o">/</span> <span class="s2">"derivatives"</span>
|
||||
<span class="o">/</span> <span class="n">directory</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">files</span> <span class="o">=</span> <span class="n">derivative_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="si">}</span><span class="s2">*.*"</span><span class="p">)</span>
|
||||
<span class="n">files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">files</span><span class="p">,</span> <span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">f</span><span class="p">:</span> <span class="n">f</span><span class="o">.</span><span class="n">stat</span><span class="p">()</span><span class="o">.</span><span class="n">st_size</span><span class="p">)</span>
|
||||
<span class="c1"># return list of filename but skip .THM files (these are actually low-res thumbnails in JPEG format but with .THM extension)</span>
|
||||
<span class="k">return</span> <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">files</span> <span class="k">if</span> <span class="n">filename</span><span class="o">.</span><span class="n">suffix</span> <span class="o">!=</span> <span class="s2">".THM"</span><span class="p">]</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">_path_derivatives_4</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">""" Return paths to all derivative (preview) files for Photos <= 4"""</span>
|
||||
<span class="n">modelid</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">"masterModelID"</span><span class="p">]</span>
|
||||
<span class="k">if</span> <span class="n">modelid</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="p">[]</span>
|
||||
<span class="n">folder_id</span><span class="p">,</span> <span class="n">file_id</span> <span class="o">=</span> <span class="n">_get_resource_loc</span><span class="p">(</span><span class="n">modelid</span><span class="p">)</span>
|
||||
<span class="n">derivatives_root</span> <span class="o">=</span> <span class="p">(</span>
|
||||
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_library_path</span><span class="p">)</span>
|
||||
<span class="o">/</span> <span class="s2">"resources"</span>
|
||||
<span class="o">/</span> <span class="s2">"proxies"</span>
|
||||
<span class="o">/</span> <span class="s2">"derivatives"</span>
|
||||
<span class="o">/</span> <span class="n">folder_id</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># photos appears to usually be in "00" subfolder but</span>
|
||||
<span class="c1"># could be elsewhere--I haven't figured out this logic yet</span>
|
||||
<span class="c1"># first see if it's in 00</span>
|
||||
|
||||
<span class="n">derivatives_path</span> <span class="o">=</span> <span class="n">derivatives_root</span> <span class="o">/</span> <span class="s2">"00"</span> <span class="o">/</span> <span class="n">file_id</span>
|
||||
<span class="k">if</span> <span class="n">derivatives_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
|
||||
<span class="n">files</span> <span class="o">=</span> <span class="n">derivatives_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">"*"</span><span class="p">)</span>
|
||||
<span class="n">files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">files</span><span class="p">,</span> <span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">f</span><span class="p">:</span> <span class="n">f</span><span class="o">.</span><span class="n">stat</span><span class="p">()</span><span class="o">.</span><span class="n">st_size</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">files</span><span class="p">]</span>
|
||||
|
||||
<span class="c1"># didn't find derivatives path</span>
|
||||
<span class="k">for</span> <span class="n">subdir</span> <span class="ow">in</span> <span class="n">derivatives_root</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">"*"</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="n">subdir</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
|
||||
<span class="n">derivatives_path</span> <span class="o">=</span> <span class="n">derivatives_root</span> <span class="o">/</span> <span class="n">subdir</span> <span class="o">/</span> <span class="n">file_id</span>
|
||||
<span class="k">if</span> <span class="n">derivatives_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
|
||||
<span class="n">files</span> <span class="o">=</span> <span class="n">derivatives_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">"*"</span><span class="p">)</span>
|
||||
<span class="n">files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">files</span><span class="p">,</span> <span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">f</span><span class="p">:</span> <span class="n">f</span><span class="o">.</span><span class="n">stat</span><span class="p">()</span><span class="o">.</span><span class="n">st_size</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">files</span><span class="p">]</span>
|
||||
|
||||
<span class="c1"># didn't find a derivatives path</span>
|
||||
<span class="k">return</span> <span class="p">[]</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span> <span class="nf">panorama</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="sd">""" Returns True if photo is a panorama, otherwise False """</span>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos.photosdb.photosdb — osxphotos 0.42.14 documentation</title>
|
||||
<title>osxphotos.photosdb.photosdb — osxphotos 0.42.19 documentation</title>
|
||||
<link rel="stylesheet" href="../../../_static/pygments.css" type="text/css" />
|
||||
<link rel="stylesheet" href="../../../_static/alabaster.css" type="text/css" />
|
||||
<script id="documentation_options" data-url_root="../../../" src="../../../_static/documentation_options.js"></script>
|
||||
@@ -933,7 +933,8 @@
|
||||
<span class="sd"> RKVersion.subType,</span>
|
||||
<span class="sd"> RKVersion.inTrashDate,</span>
|
||||
<span class="sd"> RKVersion.showInLibrary,</span>
|
||||
<span class="sd"> RKMaster.fileIsReference</span>
|
||||
<span class="sd"> RKMaster.fileIsReference,</span>
|
||||
<span class="sd"> RKMaster.importGroupUuid</span>
|
||||
<span class="sd"> FROM RKVersion, RKMaster</span>
|
||||
<span class="sd"> WHERE RKVersion.masterUuid = RKMaster.uuid"""</span>
|
||||
<span class="p">)</span>
|
||||
@@ -964,7 +965,8 @@
|
||||
<span class="sd"> RKVersion.subType,</span>
|
||||
<span class="sd"> RKVersion.inTrashDate,</span>
|
||||
<span class="sd"> RKVersion.showInLibrary,</span>
|
||||
<span class="sd"> RKMaster.fileIsReference</span>
|
||||
<span class="sd"> RKMaster.fileIsReference,</span>
|
||||
<span class="sd"> RKMaster.importGroupUuid</span>
|
||||
<span class="sd"> FROM RKVersion, RKMaster</span>
|
||||
<span class="sd"> WHERE RKVersion.masterUuid = RKMaster.uuid"""</span>
|
||||
<span class="p">)</span>
|
||||
@@ -1014,6 +1016,7 @@
|
||||
<span class="c1"># 41 RKVersion.inTrashDate</span>
|
||||
<span class="c1"># 42 RKVersion.showInLibrary -- is item visible in library (e.g. non-selected burst images are not visible)</span>
|
||||
<span class="c1"># 43 RKMaster.fileIsReference -- file is reference (imported without copying to Photos library)</span>
|
||||
<span class="c1"># 44 RKMaster.importGroupUuid -- to get date added from RKImportGroup</span>
|
||||
|
||||
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">c</span><span class="p">:</span>
|
||||
<span class="n">uuid</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
|
||||
@@ -1207,7 +1210,7 @@
|
||||
|
||||
<span class="c1"># import session not yet handled for Photos 4</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"import_session"</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"import_uuid"</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"import_uuid"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">44</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"fok_import_session"</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># get additional details from RKMaster, needed for RAW processing</span>
|
||||
@@ -1397,11 +1400,17 @@
|
||||
|
||||
<span class="c1"># get the place data</span>
|
||||
<span class="n">place_data</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s2">"SELECT modelID, defaultName, type, area "</span> <span class="s2">"FROM RKPlace "</span>
|
||||
<span class="s2">"SELECT modelID, defaultName, type, area FROM RKPlace"</span>
|
||||
<span class="p">)</span><span class="o">.</span><span class="n">fetchall</span><span class="p">()</span>
|
||||
<span class="n">places</span> <span class="o">=</span> <span class="p">{</span><span class="n">p</span><span class="p">[</span><span class="mi">0</span><span class="p">]:</span> <span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">place_data</span><span class="p">}</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_db_places</span> <span class="o">=</span> <span class="n">places</span>
|
||||
|
||||
<span class="c1"># get import data</span>
|
||||
<span class="n">import_data</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s2">"SELECT modelID, uuid, name, importDate from RKImportGroup"</span>
|
||||
<span class="p">)</span><span class="o">.</span><span class="n">fetchall</span><span class="p">()</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_db_import_group</span> <span class="o">=</span> <span class="p">{</span><span class="n">i</span><span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">import_data</span><span class="p">}</span>
|
||||
|
||||
<span class="k">for</span> <span class="n">uuid</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">:</span>
|
||||
<span class="c1"># get placeId which is then used to lookup defaultName</span>
|
||||
<span class="n">place_ids_query</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
@@ -1435,6 +1444,17 @@
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"placeNames"</span><span class="p">]</span> <span class="o">=</span> <span class="n">place_names</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"reverse_geolocation"</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span> <span class="c1"># Photos 5</span>
|
||||
|
||||
<span class="c1"># add date added</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">import_session</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db_import_group</span><span class="p">[</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"import_uuid"</span><span class="p">]</span>
|
||||
<span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"added_date"</span><span class="p">]</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">fromtimestamp</span><span class="p">(</span>
|
||||
<span class="n">import_session</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">+</span> <span class="n">TIME_DELTA</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">TypeError</span><span class="p">,</span> <span class="ne">KeyError</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"added_date"</span><span class="p">]</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">(</span><span class="mi">1970</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># build album_titles dictionary</span>
|
||||
<span class="k">for</span> <span class="n">album_id</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_details</span><span class="p">:</span>
|
||||
<span class="n">title</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dbalbum_details</span><span class="p">[</span><span class="n">album_id</span><span class="p">][</span><span class="s2">"title"</span><span class="p">]</span>
|
||||
@@ -1900,7 +1920,8 @@
|
||||
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZADJUSTMENTTIMESTAMP,</span>
|
||||
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZVISIBILITYSTATE,</span>
|
||||
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZTRASHEDDATE,</span>
|
||||
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZSAVEDASSETTYPE</span>
|
||||
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZSAVEDASSETTYPE,</span>
|
||||
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZADDEDDATE</span>
|
||||
<span class="s2"> FROM </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2"> </span>
|
||||
<span class="s2"> JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.Z_PK </span>
|
||||
<span class="s2"> ORDER BY </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZUUID """</span>
|
||||
@@ -1948,6 +1969,7 @@
|
||||
<span class="c1"># 38 ZGENERICASSET.ZVISIBILITYSTATE -- 0 if visible, 2 if not (e.g. a burst image)</span>
|
||||
<span class="c1"># 39 ZGENERICASSET.ZTRASHEDDATE -- date item placed in the trash or null if not in trash</span>
|
||||
<span class="c1"># 40 ZGENERICASSET.ZSAVEDASSETTYPE -- how item imported</span>
|
||||
<span class="c1"># 41 ZGENERICASSET.ZADDEDDATE -- date item added to the library</span>
|
||||
|
||||
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">c</span><span class="p">:</span>
|
||||
<span class="n">uuid</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
|
||||
@@ -2127,6 +2149,11 @@
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"saved_asset_type"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">40</span><span class="p">]</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"isreference"</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">40</span><span class="p">]</span> <span class="o">==</span> <span class="mi">10</span>
|
||||
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"added_date"</span><span class="p">]</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">fromtimestamp</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">41</span><span class="p">]</span> <span class="o">+</span> <span class="n">TIME_DELTA</span><span class="p">)</span>
|
||||
<span class="k">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">TypeError</span><span class="p">):</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"added_date"</span><span class="p">]</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">(</span><span class="mi">1970</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># initialize import session info which will be filled in later</span>
|
||||
<span class="c1"># not every photo has an import session so initialize all records now</span>
|
||||
<span class="n">info</span><span class="p">[</span><span class="s2">"import_session"</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
2
docs/_static/documentation_options.js
vendored
2
docs/_static/documentation_options.js
vendored
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.42.15',
|
||||
VERSION: '0.42.20',
|
||||
LANGUAGE: 'None',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos command line interface (CLI) — osxphotos 0.42.15 documentation</title>
|
||||
<title>osxphotos command line interface (CLI) — osxphotos 0.42.20 documentation</title>
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
|
||||
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Index — osxphotos 0.42.15 documentation</title>
|
||||
<title>Index — osxphotos 0.42.20 documentation</title>
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
|
||||
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
|
||||
@@ -1217,6 +1217,8 @@
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.date">date() (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.date_added">date_added() (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.date_modified">date_modified() (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
@@ -1990,6 +1992,8 @@
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.panorama">panorama() (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.path">path() (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.path_derivatives">path_derivatives() (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#osxphotos.PhotoInfo.path_edited">path_edited() (osxphotos.PhotoInfo property)</a>
|
||||
</li>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Welcome to osxphotos’s documentation! — osxphotos 0.42.15 documentation</title>
|
||||
<title>Welcome to osxphotos’s documentation! — osxphotos 0.42.20 documentation</title>
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
|
||||
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
|
||||
@@ -150,6 +150,10 @@ Alternatively, you can also run the command line utility like this: <code class=
|
||||
<h4>export default library using ‘country name/year’ as output directory (but use “NoCountry/year” if country not specified), add persons, album names, and year as keywords, write exif metadata to files when exporting, update only changed files, print verbose ouput<a class="headerlink" href="#export-default-library-using-country-name-year-as-output-directory-but-use-nocountry-year-if-country-not-specified-add-persons-album-names-and-year-as-keywords-write-exif-metadata-to-files-when-exporting-update-only-changed-files-print-verbose-ouput" title="Permalink to this headline">¶</a></h4>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">~/Desktop/export</span> <span class="pre">--directory</span> <span class="pre">"{place.name.country,NoCountry}/{created.year}"</span>  <span class="pre">--person-keyword</span> <span class="pre">--album-keyword</span> <span class="pre">--keyword-template</span> <span class="pre">"{created.year}"</span> <span class="pre">--exiftool</span> <span class="pre">--update</span> <span class="pre">--verbose</span></code></p>
|
||||
</div>
|
||||
<div class="section" id="find-all-videos-larger-than-200mb-and-add-them-to-photos-album-big-videos-creating-the-album-if-necessary">
|
||||
<h4>find all videos larger than 200MB and add them to Photos album “Big Videos” creating the album if necessary<a class="headerlink" href="#find-all-videos-larger-than-200mb-and-add-them-to-photos-album-big-videos-creating-the-album-if-necessary" title="Permalink to this headline">¶</a></h4>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">query</span> <span class="pre">--only-movies</span> <span class="pre">--min-size</span> <span class="pre">200MB</span> <span class="pre">--add-to-album</span> <span class="pre">"Big</span> <span class="pre">Videos"</span></code></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="example-uses-of-the-package">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos — osxphotos 0.42.15 documentation</title>
|
||||
<title>osxphotos — osxphotos 0.42.20 documentation</title>
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
|
||||
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
|
||||
|
||||
BIN
docs/objects.inv
BIN
docs/objects.inv
Binary file not shown.
Binary file not shown.
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos package — osxphotos 0.42.15 documentation</title>
|
||||
<title>osxphotos package — osxphotos 0.42.20 documentation</title>
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
|
||||
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
|
||||
@@ -732,6 +732,12 @@ self is not included in the returned list</p>
|
||||
<dd><p>image creation date as timezone aware datetime object</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt id="osxphotos.PhotoInfo.date_added">
|
||||
<em class="property"><span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">date_added</span></code><a class="headerlink" href="#osxphotos.PhotoInfo.date_added" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Date photo was added to the database</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt id="osxphotos.PhotoInfo.date_modified">
|
||||
<em class="property"><span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">date_modified</span></code><a class="headerlink" href="#osxphotos.PhotoInfo.date_modified" title="Permalink to this definition">¶</a></dt>
|
||||
@@ -1130,6 +1136,12 @@ Photos 5 mangles filenames upon import</p>
|
||||
<dd><p>absolute path on disk of the original picture</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt id="osxphotos.PhotoInfo.path_derivatives">
|
||||
<em class="property"><span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">path_derivatives</span></code><a class="headerlink" href="#osxphotos.PhotoInfo.path_derivatives" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first)</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt id="osxphotos.PhotoInfo.path_edited">
|
||||
<em class="property"><span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">path_edited</span></code><a class="headerlink" href="#osxphotos.PhotoInfo.path_edited" title="Permalink to this definition">¶</a></dt>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Search — osxphotos 0.42.15 documentation</title>
|
||||
<title>Search — osxphotos 0.42.20 documentation</title>
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Export your photos — osxphotos 0.42.15 documentation</title>
|
||||
<title>Export your photos — osxphotos 0.42.20 documentation</title>
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
|
||||
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.42.15"
|
||||
__version__ = "0.42.26"
|
||||
|
||||
@@ -6,19 +6,30 @@
|
||||
If these aren't important to you, I highly recommend you use Sven Marnach's excellent
|
||||
pyexiftool: https://github.com/smarnach/pyexiftool which provides more functionality """
|
||||
|
||||
import atexit
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from functools import lru_cache # pylint: disable=syntax-error
|
||||
from abc import ABC, abstractmethod
|
||||
from functools import lru_cache # pylint: disable=syntax-error
|
||||
|
||||
# exiftool -stay_open commands outputs this EOF marker after command is run
|
||||
EXIFTOOL_STAYOPEN_EOF = "{ready}"
|
||||
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():
|
||||
@@ -67,7 +78,8 @@ class _ExifToolProc:
|
||||
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):
|
||||
@@ -105,30 +117,30 @@ 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:
|
||||
logging.warning(
|
||||
f"exiftool pid {self._process.pid} did not exit, killing it"
|
||||
)
|
||||
self._process.kill()
|
||||
self._process.communicate()
|
||||
|
||||
del self._process
|
||||
self._process_running = False
|
||||
|
||||
def __del__(self):
|
||||
self._stop_proc()
|
||||
|
||||
|
||||
class ExifTool:
|
||||
""" Basic exiftool interface for reading and writing EXIF tags """
|
||||
@@ -154,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
|
||||
|
||||
@@ -186,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
|
||||
@@ -228,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.
|
||||
|
||||
@@ -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}"
|
||||
)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -488,7 +488,7 @@ class PhotoInfo:
|
||||
try:
|
||||
return self._burst_album_info
|
||||
except AttributeError:
|
||||
burst_album_info = list(self.album_info)
|
||||
burst_album_info = list(self.album_info)
|
||||
for photo in self.burst_photos:
|
||||
if photo.burst_key:
|
||||
burst_album_info.extend(photo.album_info)
|
||||
@@ -604,6 +604,23 @@ class PhotoInfo:
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def date_added(self):
|
||||
""" Date photo was added to the database """
|
||||
try:
|
||||
return self._date_added
|
||||
except AttributeError:
|
||||
added_date = self._info["added_date"]
|
||||
if added_date:
|
||||
seconds = self._info["imageTimeZoneOffsetSeconds"] or 0
|
||||
delta = timedelta(seconds=seconds)
|
||||
tz = timezone(delta)
|
||||
self._date_added = added_date.astimezone(tz=tz)
|
||||
else:
|
||||
self._date_added = None
|
||||
|
||||
return self._date_added
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
""" returns (latitude, longitude) as float in degrees or None """
|
||||
@@ -801,6 +818,60 @@ class PhotoInfo:
|
||||
|
||||
return photopath
|
||||
|
||||
@property
|
||||
def path_derivatives(self):
|
||||
""" Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first) """
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
return self._path_derivatives_4()
|
||||
|
||||
directory = self._uuid[0] # first char of uuid
|
||||
derivative_path = (
|
||||
pathlib.Path(self._db._library_path)
|
||||
/ "resources"
|
||||
/ "derivatives"
|
||||
/ directory
|
||||
)
|
||||
files = derivative_path.glob(f"{self.uuid}*.*")
|
||||
files = sorted(files, reverse=True, key=lambda f: f.stat().st_size)
|
||||
# return list of filename but skip .THM files (these are actually low-res thumbnails in JPEG format but with .THM extension)
|
||||
return [str(filename) for filename in files if filename.suffix != ".THM"]
|
||||
|
||||
def _path_derivatives_4(self):
|
||||
""" Return paths to all derivative (preview) files for Photos <= 4"""
|
||||
modelid = self._info["modelID"]
|
||||
if modelid is None:
|
||||
return []
|
||||
folder_id, file_id = _get_resource_loc(modelid)
|
||||
derivatives_root = (
|
||||
pathlib.Path(self._db._library_path)
|
||||
/ "resources"
|
||||
/ "proxies"
|
||||
/ "derivatives"
|
||||
/ folder_id
|
||||
)
|
||||
|
||||
# photos appears to usually be in "00" subfolder but
|
||||
# could be elsewhere--I haven't figured out this logic yet
|
||||
# first see if it's in 00
|
||||
|
||||
derivatives_path = derivatives_root / "00" / file_id
|
||||
if derivatives_path.is_dir():
|
||||
files = derivatives_path.glob("*")
|
||||
files = sorted(files, reverse=True, key=lambda f: f.stat().st_size)
|
||||
return [str(filename) for filename in files]
|
||||
|
||||
# didn't find derivatives path
|
||||
for subdir in derivatives_root.glob("*"):
|
||||
if subdir.is_dir():
|
||||
derivatives_path = derivatives_root / subdir / file_id
|
||||
if derivatives_path.is_dir():
|
||||
files = derivatives_path.glob("*")
|
||||
files = sorted(files, reverse=True, key=lambda f: f.stat().st_size)
|
||||
return [str(filename) for filename in files]
|
||||
|
||||
# didn't find a derivatives path
|
||||
return []
|
||||
|
||||
@property
|
||||
def panorama(self):
|
||||
""" Returns True if photo is a panorama, otherwise False """
|
||||
|
||||
@@ -900,7 +900,8 @@ class PhotosDB:
|
||||
RKVersion.subType,
|
||||
RKVersion.inTrashDate,
|
||||
RKVersion.showInLibrary,
|
||||
RKMaster.fileIsReference
|
||||
RKMaster.fileIsReference,
|
||||
RKMaster.importGroupUuid
|
||||
FROM RKVersion, RKMaster
|
||||
WHERE RKVersion.masterUuid = RKMaster.uuid"""
|
||||
)
|
||||
@@ -931,7 +932,8 @@ class PhotosDB:
|
||||
RKVersion.subType,
|
||||
RKVersion.inTrashDate,
|
||||
RKVersion.showInLibrary,
|
||||
RKMaster.fileIsReference
|
||||
RKMaster.fileIsReference,
|
||||
RKMaster.importGroupUuid
|
||||
FROM RKVersion, RKMaster
|
||||
WHERE RKVersion.masterUuid = RKMaster.uuid"""
|
||||
)
|
||||
@@ -981,6 +983,7 @@ class PhotosDB:
|
||||
# 41 RKVersion.inTrashDate
|
||||
# 42 RKVersion.showInLibrary -- is item visible in library (e.g. non-selected burst images are not visible)
|
||||
# 43 RKMaster.fileIsReference -- file is reference (imported without copying to Photos library)
|
||||
# 44 RKMaster.importGroupUuid -- to get date added from RKImportGroup
|
||||
|
||||
for row in c:
|
||||
uuid = row[0]
|
||||
@@ -1174,7 +1177,7 @@ class PhotosDB:
|
||||
|
||||
# import session not yet handled for Photos 4
|
||||
self._dbphotos[uuid]["import_session"] = None
|
||||
self._dbphotos[uuid]["import_uuid"] = None
|
||||
self._dbphotos[uuid]["import_uuid"] = row[44]
|
||||
self._dbphotos[uuid]["fok_import_session"] = None
|
||||
|
||||
# get additional details from RKMaster, needed for RAW processing
|
||||
@@ -1364,11 +1367,17 @@ class PhotosDB:
|
||||
|
||||
# get the place data
|
||||
place_data = c.execute(
|
||||
"SELECT modelID, defaultName, type, area " "FROM RKPlace "
|
||||
"SELECT modelID, defaultName, type, area FROM RKPlace"
|
||||
).fetchall()
|
||||
places = {p[0]: p for p in place_data}
|
||||
self._db_places = places
|
||||
|
||||
# get import data
|
||||
import_data = c.execute(
|
||||
"SELECT modelID, uuid, name, importDate from RKImportGroup"
|
||||
).fetchall()
|
||||
self._db_import_group = {i[1]: i for i in import_data}
|
||||
|
||||
for uuid in self._dbphotos:
|
||||
# get placeId which is then used to lookup defaultName
|
||||
place_ids_query = c.execute(
|
||||
@@ -1402,6 +1411,17 @@ class PhotosDB:
|
||||
self._dbphotos[uuid]["placeNames"] = place_names
|
||||
self._dbphotos[uuid]["reverse_geolocation"] = None # Photos 5
|
||||
|
||||
# add date added
|
||||
try:
|
||||
import_session = self._db_import_group[
|
||||
self._dbphotos[uuid]["import_uuid"]
|
||||
]
|
||||
self._dbphotos[uuid]["added_date"] = datetime.fromtimestamp(
|
||||
import_session[3] + TIME_DELTA
|
||||
)
|
||||
except (ValueError, TypeError, KeyError):
|
||||
self._dbphotos[uuid]["added_date"] = datetime(1970, 1, 1)
|
||||
|
||||
# build album_titles dictionary
|
||||
for album_id in self._dbalbum_details:
|
||||
title = self._dbalbum_details[album_id]["title"]
|
||||
@@ -1595,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],
|
||||
@@ -1867,7 +1891,8 @@ class PhotosDB:
|
||||
{asset_table}.ZADJUSTMENTTIMESTAMP,
|
||||
{asset_table}.ZVISIBILITYSTATE,
|
||||
{asset_table}.ZTRASHEDDATE,
|
||||
{asset_table}.ZSAVEDASSETTYPE
|
||||
{asset_table}.ZSAVEDASSETTYPE,
|
||||
{asset_table}.ZADDEDDATE
|
||||
FROM {asset_table}
|
||||
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
|
||||
ORDER BY {asset_table}.ZUUID """
|
||||
@@ -1915,6 +1940,7 @@ class PhotosDB:
|
||||
# 38 ZGENERICASSET.ZVISIBILITYSTATE -- 0 if visible, 2 if not (e.g. a burst image)
|
||||
# 39 ZGENERICASSET.ZTRASHEDDATE -- date item placed in the trash or null if not in trash
|
||||
# 40 ZGENERICASSET.ZSAVEDASSETTYPE -- how item imported
|
||||
# 41 ZGENERICASSET.ZADDEDDATE -- date item added to the library
|
||||
|
||||
for row in c:
|
||||
uuid = row[0]
|
||||
@@ -2094,6 +2120,11 @@ class PhotosDB:
|
||||
info["saved_asset_type"] = row[40]
|
||||
info["isreference"] = row[40] == 10
|
||||
|
||||
try:
|
||||
info["added_date"] = datetime.fromtimestamp(row[41] + TIME_DELTA)
|
||||
except (ValueError, TypeError):
|
||||
info["added_date"] = datetime(1970, 1, 1)
|
||||
|
||||
# initialize import session info which will be filled in later
|
||||
# not every photo has an import session so initialize all records now
|
||||
info["import_session"] = None
|
||||
|
||||
@@ -4,10 +4,12 @@ import datetime
|
||||
import locale
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
from textx import TextXSyntaxError, metamodel_from_file
|
||||
|
||||
from ._constants import _UNKNOWN_PERSON
|
||||
from ._version import __version__
|
||||
from .datetime_formatter import DateTimeFormatter
|
||||
from .exiftool import ExifToolCaching
|
||||
from .path_utils import sanitize_dirname, sanitize_filename, sanitize_pathpart
|
||||
@@ -134,6 +136,8 @@ TEMPLATE_SUBSTITUTIONS = {
|
||||
"{lf}": r"A line feed: '\n', alias for {newline}",
|
||||
"{cr}": r"A carriage return: '\r'",
|
||||
"{crlf}": r"a carriage return + line feed: '\r\n'",
|
||||
"{osxphotos_version}": f"The osxphotos version, e.g. '{__version__}'",
|
||||
"{osxphotos_cmd_line}": "The full command line used to run osxphotos"
|
||||
}
|
||||
|
||||
# Permitted multi-value substitutions (each of these returns None or 1 or more values)
|
||||
@@ -908,6 +912,10 @@ class PhotoTemplate:
|
||||
value = self.photo.uuid
|
||||
elif field in PUNCTUATION:
|
||||
value = PUNCTUATION[field]
|
||||
elif field == "osxphotos_version":
|
||||
value = __version__
|
||||
elif field == "osxphotos_cmd_line":
|
||||
value = " ".join(sys.argv)
|
||||
else:
|
||||
# if here, didn't get a match
|
||||
raise ValueError(f"Unhandled template value: {field}")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -34,7 +35,7 @@ if OS_VER == "15":
|
||||
TEST_LIBRARY = "tests/Test-10.15.7.photoslibrary"
|
||||
else:
|
||||
TEST_LIBRARY = None
|
||||
pytest.exit("This test suite currently only runs on MacOS Catalina ")
|
||||
# pytest.exit("This test suite currently only runs on MacOS Catalina ")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@@ -59,10 +60,13 @@ def pytest_configure(config):
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
if config.getoption("--addalbum"):
|
||||
if config.getoption("--addalbum") and TEST_LIBRARY is not None:
|
||||
# --addalbum given in cli: do not skip addalbum tests (these require interactive test)
|
||||
return
|
||||
skip_addalbum = pytest.mark.skip(reason="need --addalbum option 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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -316,6 +316,16 @@ def test_attributes(photosdb):
|
||||
assert p.date == datetime.datetime(
|
||||
2018, 9, 28, 16, 7, 7, 0, datetime.timezone(datetime.timedelta(seconds=-14400))
|
||||
)
|
||||
assert p.date_added == datetime.datetime(
|
||||
2019,
|
||||
7,
|
||||
27,
|
||||
9,
|
||||
16,
|
||||
49,
|
||||
778432,
|
||||
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=72000)),
|
||||
)
|
||||
assert p.description == "Girl holding pumpkin"
|
||||
assert p.title == "I found one!"
|
||||
assert sorted(p.albums) == ["Pumpkin Farm", "Test Album"]
|
||||
@@ -486,6 +496,21 @@ def test_path_edited2(photosdb):
|
||||
assert path is None
|
||||
|
||||
|
||||
def test_path_derivatives(photosdb):
|
||||
# test an path_derivatives
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_adjustments"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
path = p.path_derivatives
|
||||
derivs = [
|
||||
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_100_o.jpeg",
|
||||
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_105_c.jpeg",
|
||||
]
|
||||
for i, p in enumerate(path):
|
||||
assert p.endswith(derivs[i])
|
||||
|
||||
|
||||
def test_count(photosdb):
|
||||
photos = photosdb.photos()
|
||||
assert len(photos) == PHOTOS_NOT_IN_TRASH_LEN
|
||||
@@ -848,7 +873,9 @@ def test_export_12(photosdb):
|
||||
|
||||
edited_name = pathlib.Path(photos[0].path_edited).name
|
||||
edited_suffix = pathlib.Path(edited_name).suffix
|
||||
filename = pathlib.Path(photos[0].original_filename).stem + "_edited" + edited_suffix
|
||||
filename = (
|
||||
pathlib.Path(photos[0].original_filename).stem + "_edited" + edited_suffix
|
||||
)
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest, edited=True)[0]
|
||||
@@ -1098,4 +1125,4 @@ def test_no_adjustments(photosdb):
|
||||
""" test adjustments when photo has no adjusments"""
|
||||
|
||||
photo = photosdb.get_photo(UUID_DICT["no_adjustments"])
|
||||
assert photo.adjustments is None
|
||||
assert photo.adjustments is None
|
||||
|
||||
@@ -359,6 +359,16 @@ def test_attributes(photosdb):
|
||||
assert p.date == datetime.datetime(
|
||||
2018, 9, 28, 16, 7, 7, 0, datetime.timezone(datetime.timedelta(seconds=-14400))
|
||||
)
|
||||
assert p.date_added == datetime.datetime(
|
||||
2019,
|
||||
7,
|
||||
27,
|
||||
9,
|
||||
16,
|
||||
49,
|
||||
778432,
|
||||
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=72000)),
|
||||
)
|
||||
assert p.description == "Girl holding pumpkin"
|
||||
assert p.title == "I found one!"
|
||||
assert sorted(p.albums) == ["Pumpkin Farm", "Test Album"]
|
||||
@@ -546,6 +556,21 @@ def test_path_edited2(photosdb):
|
||||
assert path is None
|
||||
|
||||
|
||||
def test_path_derivatives(photosdb):
|
||||
# test an path_derivatives
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_adjustments"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
path = p.path_derivatives
|
||||
derivs = [
|
||||
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_100_o.jpeg",
|
||||
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_105_c.jpeg",
|
||||
]
|
||||
for i, p in enumerate(path):
|
||||
assert p.endswith(derivs[i])
|
||||
|
||||
|
||||
def test_ismovie(photosdb):
|
||||
# test ismovie == True
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -417,3 +417,25 @@ def test_photoinfo_exiftool_none():
|
||||
photo = photosdb.photos(uuid=[uuid])[0]
|
||||
exiftool = photo.exiftool
|
||||
assert exiftool is None
|
||||
|
||||
|
||||
def test_exiftool_terminate():
|
||||
""" Test that exiftool process is terminated when exiftool.terminate() is called """
|
||||
import osxphotos.exiftool
|
||||
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"
|
||||
|
||||
@@ -244,6 +244,15 @@ def test_attributes(photosdb):
|
||||
assert p.date == datetime.datetime(
|
||||
2018, 9, 28, 16, 7, 7, 0, datetime.timezone(datetime.timedelta(seconds=-14400))
|
||||
)
|
||||
assert p.date_added == datetime.datetime(
|
||||
2019,
|
||||
7,
|
||||
27,
|
||||
9,
|
||||
16,
|
||||
50,
|
||||
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=72000)),
|
||||
)
|
||||
assert p.description == "Girl holding pumpkin"
|
||||
assert p.title == "I found one!"
|
||||
assert sorted(p.albums) == sorted(
|
||||
@@ -410,6 +419,20 @@ def test_path_edited1(photosdb):
|
||||
assert os.path.exists(path)
|
||||
|
||||
|
||||
def test_path_derivatives(photosdb):
|
||||
# test path_derivatives (not currently implemented for Photos <= 4)
|
||||
import os.path
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
|
||||
p = photos[0]
|
||||
derivs = [
|
||||
"/resources/proxies/derivatives/00/00/1/Y6OofYkbR96spbS6XgwOQw_thumb_1.jpg",
|
||||
"/resources/proxies/derivatives/00/00/1/Y6OofYkbR96spbS6XgwOQw_mini_1.jpg",
|
||||
]
|
||||
for i, p in enumerate(p.path_derivatives):
|
||||
assert p.endswith(derivs[i])
|
||||
|
||||
|
||||
def test_path_edited2(photosdb):
|
||||
# test an invalid edited path
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_adjustments"]])
|
||||
@@ -651,4 +674,4 @@ def test_no_adjustments(photosdb):
|
||||
""" test adjustments when photo has no adjusments"""
|
||||
|
||||
photo = photosdb.get_photo(UUID_DICT["no_adjustments"])
|
||||
assert photo.adjustments is None
|
||||
assert photo.adjustments is None
|
||||
|
||||
Reference in New Issue
Block a user