Fixed implementation of use_albums_as_keywords and use_persons_as_keywords, closes #115

This commit is contained in:
Rhet Turnbull
2020-04-28 07:41:37 -07:00
parent a80071111f
commit 1ceda15134
5 changed files with 65 additions and 37 deletions

View File

@@ -963,7 +963,7 @@ Returns True if photo is a panorama, otherwise False.
Returns a JSON representation of all photo info Returns a JSON representation of all photo info
#### `export()` #### `export()`
`export(dest, *filename, edited=False, live_photo=False, overwrite=False, increment=True, sidecar_json=False, sidecar_xmp=False, use_photos_export=False, timeout=120, exiftool=False, no_xattr=False)` `export(dest, *filename, edited=False, live_photo=False, overwrite=False, increment=True, sidecar_json=False, sidecar_xmp=False, use_photos_export=False, timeout=120, exiftool=False, no_xattr=False, use_albums_as_keywords=False, use_persons_as_keywords=False)`
Export photo from the Photos library to another destination on disk. Export photo from the Photos library to another destination on disk.
- dest: must be valid destination path as str (or exception raised). - dest: must be valid destination path as str (or exception raised).
@@ -978,6 +978,8 @@ Export photo from the Photos library to another destination on disk.
- timeout: (int, default=120) timeout in seconds used with use_photos_export - timeout: (int, default=120) timeout in seconds used with use_photos_export
- exiftool: (boolean, default = False) if True, will use [exiftool](https://exiftool.org/) to write metadata directly to the exported photo; exiftool must be installed and in the system path - exiftool: (boolean, default = False) if True, will use [exiftool](https://exiftool.org/) to write metadata directly to the exported photo; exiftool must be installed and in the system path
- no_xattr: (boolean, default = False); if True, exports file without preserving extended attributes - no_xattr: (boolean, default = False); if True, exports file without preserving extended attributes
- use_albums_as_keywords: (boolean, default = False); if True, will use album names as keywords when exporting metadata with exiftool or sidecar
- use_persons_as_keywords: (boolean, default = False); if True, will use person names as keywords when exporting metadata with exiftool or sidecar
Returns: list of paths to exported files. More than one file could be exported, for example if live_photo=True, both the original imaage and the associated .mov file will be exported Returns: list of paths to exported files. More than one file could be exported, for example if live_photo=True, both the original imaage and the associated .mov file will be exported

View File

@@ -896,12 +896,12 @@ def query(
@click.option( @click.option(
"--person-keyword", "--person-keyword",
is_flag=True, is_flag=True,
help = "Use person in image as keyword/tag when exporting metadata." help="Use person in image as keyword/tag when exporting metadata.",
) )
@click.option( @click.option(
"--album-keyword", "--album-keyword",
is_flag=True, is_flag=True,
help = "Use album name as keyword/tag when exporting metadata." help="Use album name as keyword/tag when exporting metadata.",
) )
@click.option( @click.option(
"--current-name", "--current-name",
@@ -1157,16 +1157,6 @@ def export(
) )
if photos: if photos:
# set the persons_as_keywords and albums_as_keywords on the database object
# to control metadata export
photosdb = photos[0]._db
if person_keyword:
# add persons as keywords
photosdb.use_persons_as_keywords = True
if album_keyword:
# add albums as keywords
photosdb.use_albums_as_keywords = True
if export_bursts: if export_bursts:
# add the burst_photos to the export set # add the burst_photos to the export set
photos_burst = [p for p in photos if p.burst] photos_burst = [p for p in photos if p.burst]
@@ -1196,6 +1186,8 @@ def export(
directory, directory,
no_extended_attributes, no_extended_attributes,
export_raw, export_raw,
album_keyword,
person_keyword,
) )
else: else:
for p in photos: for p in photos:
@@ -1214,6 +1206,8 @@ def export(
directory, directory,
no_extended_attributes, no_extended_attributes,
export_raw, export_raw,
album_keyword,
person_keyword,
) )
if export_paths: if export_paths:
click.echo(f"Exported {p.filename} to {export_paths}") click.echo(f"Exported {p.filename} to {export_paths}")
@@ -1600,6 +1594,8 @@ def export_photo(
directory, directory,
no_extended_attributes, no_extended_attributes,
export_raw, export_raw,
album_keyword,
person_keyword,
): ):
""" Helper function for export that does the actual export """ Helper function for export that does the actual export
photo: PhotoInfo object photo: PhotoInfo object
@@ -1616,6 +1612,8 @@ def export_photo(
directory: template used to determine output directory directory: template used to determine output directory
no_extended_attributes: boolean; if True, exports photo without preserving extended attributes no_extended_attributes: boolean; if True, exports photo without preserving extended attributes
export_raw: boolean; if True exports RAW image associate with the photo export_raw: boolean; if True exports RAW image associate with the photo
album_keyword: boolean; if True, exports album names as keywords in metadata
person_keyword: boolean; if True, exports person names as keywords in metadata
returns list of path(s) of exported photo or None if photo was missing returns list of path(s) of exported photo or None if photo was missing
""" """
@@ -1699,6 +1697,8 @@ def export_photo(
use_photos_export=use_photos_export, use_photos_export=use_photos_export,
exiftool=exiftool, exiftool=exiftool,
no_xattr=no_extended_attributes, no_xattr=no_extended_attributes,
use_albums_as_keywords=album_keyword,
use_persons_as_keywords=person_keyword,
)[0] )[0]
photo_paths.append(photo_path) photo_paths.append(photo_path)
@@ -1734,6 +1734,8 @@ def export_photo(
use_photos_export=use_photos_export, use_photos_export=use_photos_export,
exiftool=exiftool, exiftool=exiftool,
no_xattr=no_extended_attributes, no_xattr=no_extended_attributes,
use_albums_as_keywords=album_keyword,
use_persons_as_keywords=person_keyword,
) )
return photo_paths return photo_paths

View File

@@ -1,3 +1,3 @@
""" version info """ """ version info """
__version__ = "0.28.7" __version__ = "0.28.8"

View File

@@ -634,6 +634,8 @@ class PhotoInfo:
timeout=120, timeout=120,
exiftool=False, exiftool=False,
no_xattr=False, no_xattr=False,
use_albums_as_keywords=False,
use_persons_as_keywords=False,
): ):
""" export photo """ export photo
dest: must be valid destination path (or exception raised) dest: must be valid destination path (or exception raised)
@@ -659,7 +661,12 @@ class PhotoInfo:
timeout: (int, default=120) timeout in seconds used with use_photos_export timeout: (int, default=120) timeout in seconds used with use_photos_export
exiftool: (boolean, default = False); if True, will use exiftool to write metadata to export file exiftool: (boolean, default = False); if True, will use exiftool to write metadata to export file
no_xattr: (boolean, default = False); if True, exports file without preserving extended attributes no_xattr: (boolean, default = False); if True, exports file without preserving extended attributes
returns list of full paths to the exported files """ returns list of full paths to the exported files
use_albums_as_keywords: (boolean, default = False); if True, will include album names in keywords
when exporting metadata with exiftool or sidecar
use_persons_as_keywords: (boolean, default = False); if True, will include person names in keywords
when exporting metadata with exiftool or sidecar
"""
# list of all files exported during this call to export # list of all files exported during this call to export
exported_files = [] exported_files = []
@@ -863,7 +870,10 @@ class PhotoInfo:
if sidecar_json: if sidecar_json:
logging.debug("writing exiftool_json_sidecar") logging.debug("writing exiftool_json_sidecar")
sidecar_filename = dest.parent / pathlib.Path(f"{dest.stem}.json") sidecar_filename = dest.parent / pathlib.Path(f"{dest.stem}.json")
sidecar_str = self._exiftool_json_sidecar() sidecar_str = self._exiftool_json_sidecar(
use_albums_as_keywords=use_albums_as_keywords,
use_persons_as_keywords=use_persons_as_keywords,
)
try: try:
self._write_sidecar(sidecar_filename, sidecar_str) self._write_sidecar(sidecar_filename, sidecar_str)
except Exception as e: except Exception as e:
@@ -873,7 +883,10 @@ class PhotoInfo:
if sidecar_xmp: if sidecar_xmp:
logging.debug("writing xmp_sidecar") logging.debug("writing xmp_sidecar")
sidecar_filename = dest.parent / pathlib.Path(f"{dest.stem}.xmp") sidecar_filename = dest.parent / pathlib.Path(f"{dest.stem}.xmp")
sidecar_str = self._xmp_sidecar() sidecar_str = self._xmp_sidecar(
use_albums_as_keywords=use_albums_as_keywords,
use_persons_as_keywords=use_persons_as_keywords,
)
try: try:
self._write_sidecar(sidecar_filename, sidecar_str) self._write_sidecar(sidecar_filename, sidecar_str)
except Exception as e: except Exception as e:
@@ -883,17 +896,28 @@ class PhotoInfo:
# if exiftool, write the metadata # if exiftool, write the metadata
if exiftool and exported_files: if exiftool and exported_files:
for exported_file in exported_files: for exported_file in exported_files:
self._write_exif_data(exported_file) self._write_exif_data(
exported_file,
use_albums_as_keywords=use_albums_as_keywords,
use_persons_as_keywords=use_persons_as_keywords,
)
return exported_files return exported_files
def _write_exif_data(self, filepath): def _write_exif_data(
self, filepath, use_albums_as_keywords=False, use_persons_as_keywords=False
):
""" write exif data to image file at filepath """ write exif data to image file at filepath
filepath: full path to the image file """ filepath: full path to the image file """
if not os.path.exists(filepath): if not os.path.exists(filepath):
raise FileNotFoundError(f"Could not find file {filepath}") raise FileNotFoundError(f"Could not find file {filepath}")
exiftool = ExifTool(filepath) exiftool = ExifTool(filepath)
exif_info = json.loads(self._exiftool_json_sidecar())[0] exif_info = json.loads(
self._exiftool_json_sidecar(
use_albums_as_keywords=use_albums_as_keywords,
use_persons_as_keywords=use_persons_as_keywords,
)
)[0]
for exiftag, val in exif_info.items(): for exiftag, val in exif_info.items():
if type(val) == list: if type(val) == list:
# more than one, set first value the add additional values # more than one, set first value the add additional values
@@ -904,7 +928,9 @@ class PhotoInfo:
else: else:
exiftool.setvalue(exiftag, val) exiftool.setvalue(exiftag, val)
def _exiftool_json_sidecar(self): def _exiftool_json_sidecar(
self, use_albums_as_keywords=False, use_persons_as_keywords=False
):
""" return json string of EXIF details in exiftool sidecar format """ return json string of EXIF details in exiftool sidecar format
Does not include all the EXIF fields as those are likely already in the image Does not include all the EXIF fields as those are likely already in the image
Exports the following: Exports the following:
@@ -942,10 +968,10 @@ class PhotoInfo:
# filter out _UNKNOWN_PERSON # filter out _UNKNOWN_PERSON
person_list = [p for p in self.persons if p != _UNKNOWN_PERSON] person_list = [p for p in self.persons if p != _UNKNOWN_PERSON]
if self._db.use_persons_as_keywords and person_list: if use_persons_as_keywords and person_list:
keyword_list.extend(person_list) keyword_list.extend(person_list)
if self._db.use_albums_as_keywords and self.albums: if use_albums_as_keywords and self.albums:
keyword_list.extend(self.albums) keyword_list.extend(self.albums)
if keyword_list: if keyword_list:
@@ -990,7 +1016,7 @@ class PhotoInfo:
json_str = json.dumps([exif]) json_str = json.dumps([exif])
return json_str return json_str
def _xmp_sidecar(self): def _xmp_sidecar(self, use_albums_as_keywords=False, use_persons_as_keywords=False):
""" returns string for XMP sidecar """ """ returns string for XMP sidecar """
# TODO: add additional fields to XMP file? # TODO: add additional fields to XMP file?
@@ -1007,10 +1033,10 @@ class PhotoInfo:
# filter out _UNKNOWN_PERSON # filter out _UNKNOWN_PERSON
person_list = [p for p in self.persons if p != _UNKNOWN_PERSON] person_list = [p for p in self.persons if p != _UNKNOWN_PERSON]
if self._db.use_persons_as_keywords and person_list: if use_persons_as_keywords and person_list:
keyword_list.extend(person_list) keyword_list.extend(person_list)
if self._db.use_albums_as_keywords and self.albums: if use_albums_as_keywords and self.albums:
keyword_list.extend(self.albums) keyword_list.extend(self.albums)
subject_list = [] subject_list = []

View File

@@ -487,7 +487,6 @@ def test_exiftool_json_sidecar_use_persons_keyword():
import json import json
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photosdb.use_persons_as_keywords = True
photos = photosdb.photos(uuid=[UUID_DICT["xmp"]]) photos = photosdb.photos(uuid=[UUID_DICT["xmp"]])
json_expected = json.loads( json_expected = json.loads(
@@ -506,7 +505,7 @@ def test_exiftool_json_sidecar_use_persons_keyword():
""" """
)[0] )[0]
json_got = photos[0]._exiftool_json_sidecar() json_got = photos[0]._exiftool_json_sidecar(use_persons_as_keywords=True)
json_got = json.loads(json_got)[0] json_got = json.loads(json_got)[0]
# some gymnastics to account for different sort order in different pythons # some gymnastics to account for different sort order in different pythons
@@ -529,7 +528,6 @@ def test_exiftool_json_sidecar_use_albums_keyword():
import json import json
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photosdb.use_albums_as_keywords = True
photos = photosdb.photos(uuid=[UUID_DICT["xmp"]]) photos = photosdb.photos(uuid=[UUID_DICT["xmp"]])
json_expected = json.loads( json_expected = json.loads(
@@ -548,7 +546,7 @@ def test_exiftool_json_sidecar_use_albums_keyword():
""" """
)[0] )[0]
json_got = photos[0]._exiftool_json_sidecar() json_got = photos[0]._exiftool_json_sidecar(use_albums_as_keywords=True)
json_got = json.loads(json_got)[0] json_got = json.loads(json_got)[0]
# some gymnastics to account for different sort order in different pythons # some gymnastics to account for different sort order in different pythons
@@ -624,11 +622,11 @@ def test_xmp_sidecar():
for line_expected, line_got in zip(xmp_expected_lines, xmp_got_lines): for line_expected, line_got in zip(xmp_expected_lines, xmp_got_lines):
assert line_expected == line_got assert line_expected == line_got
def test_xmp_sidecar_use_persons_keyword(): def test_xmp_sidecar_use_persons_keyword():
import osxphotos import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photosdb.use_persons_as_keywords = True
photos = photosdb.photos(uuid=[UUID_DICT["xmp"]]) photos = photosdb.photos(uuid=[UUID_DICT["xmp"]])
xmp_expected = """<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos --> xmp_expected = """<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
@@ -679,17 +677,17 @@ def test_xmp_sidecar_use_persons_keyword():
xmp_expected_lines = [line.strip() for line in xmp_expected.split("\n")] xmp_expected_lines = [line.strip() for line in xmp_expected.split("\n")]
xmp_got = photos[0]._xmp_sidecar() xmp_got = photos[0]._xmp_sidecar(use_persons_as_keywords=True)
xmp_got_lines = [line.strip() for line in xmp_got.split("\n")] xmp_got_lines = [line.strip() for line in xmp_got.split("\n")]
for line_expected, line_got in zip(xmp_expected_lines, xmp_got_lines): for line_expected, line_got in zip(xmp_expected_lines, xmp_got_lines):
assert line_expected == line_got assert line_expected == line_got
def test_xmp_sidecar_use_albums_keyword(): def test_xmp_sidecar_use_albums_keyword():
import osxphotos import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photosdb.use_albums_as_keywords = True
photos = photosdb.photos(uuid=[UUID_DICT["xmp"]]) photos = photosdb.photos(uuid=[UUID_DICT["xmp"]])
xmp_expected = """<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos --> xmp_expected = """<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
@@ -740,7 +738,7 @@ def test_xmp_sidecar_use_albums_keyword():
xmp_expected_lines = [line.strip() for line in xmp_expected.split("\n")] xmp_expected_lines = [line.strip() for line in xmp_expected.split("\n")]
xmp_got = photos[0]._xmp_sidecar() xmp_got = photos[0]._xmp_sidecar(use_albums_as_keywords=True)
xmp_got_lines = [line.strip() for line in xmp_got.split("\n")] xmp_got_lines = [line.strip() for line in xmp_got.split("\n")]
for line_expected, line_got in zip(xmp_expected_lines, xmp_got_lines): for line_expected, line_got in zip(xmp_expected_lines, xmp_got_lines):