Fixed implementation of use_albums_as_keywords and use_persons_as_keywords, closes #115
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -895,13 +895,13 @@ 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
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.28.7"
|
__version__ = "0.28.8"
|
||||||
|
|||||||
@@ -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 = []
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user