Added --album-keyword and --person-keyword to CLI, closes #61
This commit is contained in:
13
README.md
13
README.md
@@ -217,6 +217,10 @@ Options:
|
|||||||
photos if the RAW photo does not have an
|
photos if the RAW photo does not have an
|
||||||
associated jpeg image (e.g. the RAW file was
|
associated jpeg image (e.g. the RAW file was
|
||||||
imported to Photos without a jpeg preview).
|
imported to Photos without a jpeg preview).
|
||||||
|
--person-keyword Use person in image as keyword/tag when
|
||||||
|
exporting metadata.
|
||||||
|
--album-keyword Use album name as keyword/tag when exporting
|
||||||
|
metadata.
|
||||||
--current-name Use photo's current filename instead of
|
--current-name Use photo's current filename instead of
|
||||||
original filename for export. Note:
|
original filename for export. Note:
|
||||||
Starting with Photos 5, all photos are
|
Starting with Photos 5, all photos are
|
||||||
@@ -814,6 +818,12 @@ For example, in my library, Photos says I have 19,386 photos and 474 movies. Ho
|
|||||||
>>>
|
>>>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `use_persons_as_keywords`
|
||||||
|
If True, person names (face/person in image) will be used as keywords when exporting metadata with [PhotoInfo](#PhotoInfo) [export()](#export).
|
||||||
|
|
||||||
|
#### `use_albums_as_keywords`
|
||||||
|
If True, album names will be used as keywords when exporting metadata with [PhotoInfo](#PhotoInfo) [export()](#export).
|
||||||
|
|
||||||
### PhotoInfo
|
### PhotoInfo
|
||||||
PhotosDB.photos() returns a list of PhotoInfo objects. Each PhotoInfo object represents a single photo in the Photos library.
|
PhotosDB.photos() returns a list of PhotoInfo objects. Each PhotoInfo object represents a single photo in the Photos library.
|
||||||
|
|
||||||
@@ -958,7 +968,8 @@ Returns True if photo is a panorama, otherwise False.
|
|||||||
#### `json()`
|
#### `json()`
|
||||||
Returns a JSON representation of all photo info
|
Returns a JSON representation of all photo info
|
||||||
|
|
||||||
#### `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()`
|
||||||
|
`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 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).
|
||||||
|
|||||||
@@ -893,6 +893,16 @@ def query(
|
|||||||
"Note: this does not skip RAW photos if the RAW photo does not have an associated jpeg image "
|
"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).",
|
"(e.g. the RAW file was imported to Photos without a jpeg preview).",
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
"--person-keyword",
|
||||||
|
is_flag = True,
|
||||||
|
help = "Use person in image as keyword/tag when exporting metadata."
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--album-keyword",
|
||||||
|
is_flag = True,
|
||||||
|
help = "Use album name as keyword/tag when exporting metadata."
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--current-name",
|
"--current-name",
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
@@ -983,6 +993,8 @@ def export(
|
|||||||
skip_bursts,
|
skip_bursts,
|
||||||
skip_live,
|
skip_live,
|
||||||
skip_raw,
|
skip_raw,
|
||||||
|
person_keyword,
|
||||||
|
album_keyword,
|
||||||
current_name,
|
current_name,
|
||||||
sidecar,
|
sidecar,
|
||||||
only_photos,
|
only_photos,
|
||||||
@@ -1145,6 +1157,16 @@ 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]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.28.6"
|
__version__ = "0.28.7"
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from ._constants import (
|
|||||||
_PHOTOS_4_VERSION,
|
_PHOTOS_4_VERSION,
|
||||||
_PHOTOS_5_SHARED_PHOTO_PATH,
|
_PHOTOS_5_SHARED_PHOTO_PATH,
|
||||||
_TEMPLATE_DIR,
|
_TEMPLATE_DIR,
|
||||||
|
_UNKNOWN_PERSON,
|
||||||
_XMP_TEMPLATE_NAME,
|
_XMP_TEMPLATE_NAME,
|
||||||
)
|
)
|
||||||
from .exiftool import ExifTool
|
from .exiftool import ExifTool
|
||||||
@@ -304,9 +305,7 @@ class PhotoInfo:
|
|||||||
# Note: In Photos Version 5.0 (141.19.150), images not copied to Photos Library
|
# Note: In Photos Version 5.0 (141.19.150), images not copied to Photos Library
|
||||||
# that are missing do not always trigger is_missing = True as happens
|
# that are missing do not always trigger is_missing = True as happens
|
||||||
# in earlier version so it's possible for this check to fail, if so, return None
|
# in earlier version so it's possible for this check to fail, if so, return None
|
||||||
logging.debug(
|
logging.debug(f"Error getting path to RAW file: {filepath}/{glob_str}")
|
||||||
f"Error getting path to RAW file: {filepath}/{glob_str}"
|
|
||||||
)
|
|
||||||
photopath = None
|
photopath = None
|
||||||
else:
|
else:
|
||||||
photopath = os.path.join(filepath, raw_file[0])
|
photopath = os.path.join(filepath, raw_file[0])
|
||||||
@@ -934,18 +933,30 @@ class PhotoInfo:
|
|||||||
if self.title:
|
if self.title:
|
||||||
exif["XMP:Title"] = self.title
|
exif["XMP:Title"] = self.title
|
||||||
|
|
||||||
|
keyword_list = []
|
||||||
if self.keywords:
|
if self.keywords:
|
||||||
exif["XMP:TagsList"] = exif["IPTC:Keywords"] = list(self.keywords)
|
keyword_list.extend(self.keywords)
|
||||||
# Photos puts both keywords and persons in Subject when using "Export IPTC as XMP"
|
|
||||||
exif["XMP:Subject"] = list(self.keywords)
|
|
||||||
|
|
||||||
|
person_list = []
|
||||||
if self.persons:
|
if self.persons:
|
||||||
exif["XMP:PersonInImage"] = self.persons
|
# filter out _UNKNOWN_PERSON
|
||||||
|
person_list = [p for p in self.persons if p != _UNKNOWN_PERSON]
|
||||||
|
|
||||||
|
if self._db.use_persons_as_keywords and person_list:
|
||||||
|
keyword_list.extend(person_list)
|
||||||
|
|
||||||
|
if self._db.use_albums_as_keywords and self.albums:
|
||||||
|
keyword_list.extend(self.albums)
|
||||||
|
|
||||||
|
if keyword_list:
|
||||||
|
exif["XMP:TagsList"] = exif["IPTC:Keywords"] = keyword_list
|
||||||
|
|
||||||
|
if person_list:
|
||||||
|
exif["XMP:PersonInImage"] = person_list
|
||||||
|
|
||||||
|
if self.keywords or person_list:
|
||||||
# Photos puts both keywords and persons in Subject when using "Export IPTC as XMP"
|
# Photos puts both keywords and persons in Subject when using "Export IPTC as XMP"
|
||||||
if "XMP:Subject" in exif:
|
exif["XMP:Subject"] = list(self.keywords) + person_list
|
||||||
exif["XMP:Subject"].extend(self.persons)
|
|
||||||
else:
|
|
||||||
exif["XMP:Subject"] = self.persons
|
|
||||||
|
|
||||||
# if self.favorite():
|
# if self.favorite():
|
||||||
# exif["Rating"] = 5
|
# exif["Rating"] = 5
|
||||||
@@ -986,7 +997,34 @@ class PhotoInfo:
|
|||||||
xmp_template = Template(
|
xmp_template = Template(
|
||||||
filename=os.path.join(_TEMPLATE_DIR, _XMP_TEMPLATE_NAME)
|
filename=os.path.join(_TEMPLATE_DIR, _XMP_TEMPLATE_NAME)
|
||||||
)
|
)
|
||||||
xmp_str = xmp_template.render(photo=self)
|
|
||||||
|
keyword_list = []
|
||||||
|
if self.keywords:
|
||||||
|
keyword_list.extend(self.keywords)
|
||||||
|
|
||||||
|
person_list = []
|
||||||
|
if self.persons:
|
||||||
|
# filter out _UNKNOWN_PERSON
|
||||||
|
person_list = [p for p in self.persons if p != _UNKNOWN_PERSON]
|
||||||
|
|
||||||
|
if self._db.use_persons_as_keywords and person_list:
|
||||||
|
keyword_list.extend(person_list)
|
||||||
|
|
||||||
|
if self._db.use_albums_as_keywords and self.albums:
|
||||||
|
keyword_list.extend(self.albums)
|
||||||
|
|
||||||
|
subject_list = []
|
||||||
|
if self.keywords or person_list:
|
||||||
|
# Photos puts both keywords and persons in Subject when using "Export IPTC as XMP"
|
||||||
|
subject_list = list(self.keywords) + person_list
|
||||||
|
|
||||||
|
xmp_str = xmp_template.render(
|
||||||
|
photo=self,
|
||||||
|
keywords=keyword_list,
|
||||||
|
persons=person_list,
|
||||||
|
subjects=subject_list,
|
||||||
|
)
|
||||||
|
|
||||||
# remove extra lines that mako inserts from template
|
# remove extra lines that mako inserts from template
|
||||||
xmp_str = "\n".join(
|
xmp_str = "\n".join(
|
||||||
[line for line in xmp_str.split("\n") if line.strip() != ""]
|
[line for line in xmp_str.split("\n") if line.strip() != ""]
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ class PhotosDB:
|
|||||||
|
|
||||||
# set up the data structures used to store all the Photo database info
|
# set up the data structures used to store all the Photo database info
|
||||||
|
|
||||||
|
# if True, will treat persons as keywords when exporting metadata
|
||||||
|
self.use_persons_as_keywords = False
|
||||||
|
|
||||||
|
# if True, will treat albums as keywords when exporting metadata
|
||||||
|
self.use_albums_as_keywords = False
|
||||||
|
|
||||||
# Path to the Photos library database file
|
# Path to the Photos library database file
|
||||||
# photos.db in the photos library database/ directory
|
# photos.db in the photos library database/ directory
|
||||||
self._dbfile = None
|
self._dbfile = None
|
||||||
|
|||||||
@@ -79,16 +79,16 @@
|
|||||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||||
${dc_description(photo.description)}
|
${dc_description(photo.description)}
|
||||||
${dc_title(photo.title)}
|
${dc_title(photo.title)}
|
||||||
${dc_subject(photo.keywords + photo.persons)}
|
${dc_subject(subjects)}
|
||||||
${dc_datecreated(photo.date)}
|
${dc_datecreated(photo.date)}
|
||||||
</rdf:Description>
|
</rdf:Description>
|
||||||
<rdf:Description rdf:about=''
|
<rdf:Description rdf:about=''
|
||||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||||
${iptc_personinimage(photo.persons)}
|
${iptc_personinimage(persons)}
|
||||||
</rdf:Description>
|
</rdf:Description>
|
||||||
<rdf:Description rdf:about=''
|
<rdf:Description rdf:about=''
|
||||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||||
${dk_tagslist(photo.keywords)}
|
${dk_tagslist(keywords)}
|
||||||
</rdf:Description>
|
</rdf:Description>
|
||||||
<rdf:Description rdf:about=''
|
<rdf:Description rdf:about=''
|
||||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||||
|
|||||||
@@ -482,6 +482,90 @@ def test_exiftool_json_sidecar():
|
|||||||
assert json_got[k] == v
|
assert json_got[k] == v
|
||||||
|
|
||||||
|
|
||||||
|
def test_exiftool_json_sidecar_use_persons_keyword():
|
||||||
|
import osxphotos
|
||||||
|
import json
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
photosdb.use_persons_as_keywords = True
|
||||||
|
photos = photosdb.photos(uuid=[UUID_DICT["xmp"]])
|
||||||
|
|
||||||
|
json_expected = json.loads(
|
||||||
|
"""
|
||||||
|
[{"_CreatedBy": "osxphotos, https://github.com/RhetTbull/osxphotos",
|
||||||
|
"EXIF:ImageDescription": "Girls with pumpkins",
|
||||||
|
"XMP:Description": "Girls with pumpkins",
|
||||||
|
"XMP:Title": "Can we carry this?",
|
||||||
|
"XMP:TagsList": ["Kids", "Suzy", "Katie"],
|
||||||
|
"IPTC:Keywords": ["Kids", "Suzy", "Katie"],
|
||||||
|
"XMP:PersonInImage": ["Suzy", "Katie"],
|
||||||
|
"XMP:Subject": ["Kids", "Suzy", "Katie"],
|
||||||
|
"EXIF:DateTimeOriginal": "2018:09:28 15:35:49",
|
||||||
|
"EXIF:OffsetTimeOriginal": "-04:00",
|
||||||
|
"EXIF:ModifyDate": "2019:11:24 13:09:17"}]
|
||||||
|
"""
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
json_got = photos[0]._exiftool_json_sidecar()
|
||||||
|
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
|
||||||
|
for k, v in json_got.items():
|
||||||
|
if type(v) in (list, tuple):
|
||||||
|
assert sorted(json_expected[k]) == sorted(v)
|
||||||
|
else:
|
||||||
|
assert json_expected[k] == v
|
||||||
|
|
||||||
|
for k, v in json_expected.items():
|
||||||
|
if type(v) in (list, tuple):
|
||||||
|
assert sorted(json_got[k]) == sorted(v)
|
||||||
|
else:
|
||||||
|
assert json_got[k] == v
|
||||||
|
|
||||||
|
|
||||||
|
def test_exiftool_json_sidecar_use_albums_keyword():
|
||||||
|
import osxphotos
|
||||||
|
import json
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
photosdb.use_albums_as_keywords = True
|
||||||
|
photos = photosdb.photos(uuid=[UUID_DICT["xmp"]])
|
||||||
|
|
||||||
|
json_expected = json.loads(
|
||||||
|
"""
|
||||||
|
[{"_CreatedBy": "osxphotos, https://github.com/RhetTbull/osxphotos",
|
||||||
|
"EXIF:ImageDescription": "Girls with pumpkins",
|
||||||
|
"XMP:Description": "Girls with pumpkins",
|
||||||
|
"XMP:Title": "Can we carry this?",
|
||||||
|
"XMP:TagsList": ["Kids", "Pumpkin Farm", "Test Album"],
|
||||||
|
"IPTC:Keywords": ["Kids", "Pumpkin Farm", "Test Album"],
|
||||||
|
"XMP:PersonInImage": ["Suzy", "Katie"],
|
||||||
|
"XMP:Subject": ["Kids", "Suzy", "Katie"],
|
||||||
|
"EXIF:DateTimeOriginal": "2018:09:28 15:35:49",
|
||||||
|
"EXIF:OffsetTimeOriginal": "-04:00",
|
||||||
|
"EXIF:ModifyDate": "2019:11:24 13:09:17"}]
|
||||||
|
"""
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
json_got = photos[0]._exiftool_json_sidecar()
|
||||||
|
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
|
||||||
|
for k, v in json_got.items():
|
||||||
|
if type(v) in (list, tuple):
|
||||||
|
assert sorted(json_expected[k]) == sorted(v)
|
||||||
|
else:
|
||||||
|
assert json_expected[k] == v
|
||||||
|
|
||||||
|
for k, v in json_expected.items():
|
||||||
|
if type(v) in (list, tuple):
|
||||||
|
assert sorted(json_got[k]) == sorted(v)
|
||||||
|
else:
|
||||||
|
assert json_got[k] == v
|
||||||
|
|
||||||
|
|
||||||
def test_xmp_sidecar():
|
def test_xmp_sidecar():
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
|
||||||
@@ -539,3 +623,125 @@ 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():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
photosdb.use_persons_as_keywords = True
|
||||||
|
photos = photosdb.photos(uuid=[UUID_DICT["xmp"]])
|
||||||
|
|
||||||
|
xmp_expected = """<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||||
|
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||||
|
<!-- mirrors Photos 5 "Export IPTC as XMP" option -->
|
||||||
|
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||||
|
<rdf:Description rdf:about=""
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||||
|
<dc:description>Girls with pumpkins</dc:description>
|
||||||
|
<dc:title>Can we carry this?</dc:title>
|
||||||
|
<!-- keywords and persons listed in <dc:subject> as Photos does -->
|
||||||
|
<dc:subject>
|
||||||
|
<rdf:Seq>
|
||||||
|
<rdf:li>Kids</rdf:li>
|
||||||
|
<rdf:li>Suzy</rdf:li>
|
||||||
|
<rdf:li>Katie</rdf:li>
|
||||||
|
</rdf:Seq>
|
||||||
|
</dc:subject>
|
||||||
|
<photoshop:DateCreated>2018-09-28T15:35:49.063000-04:00</photoshop:DateCreated>
|
||||||
|
</rdf:Description>
|
||||||
|
<rdf:Description rdf:about=''
|
||||||
|
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||||
|
<Iptc4xmpExt:PersonInImage>
|
||||||
|
<rdf:Bag>
|
||||||
|
<rdf:li>Suzy</rdf:li>
|
||||||
|
<rdf:li>Katie</rdf:li>
|
||||||
|
</rdf:Bag>
|
||||||
|
</Iptc4xmpExt:PersonInImage>
|
||||||
|
</rdf:Description>
|
||||||
|
<rdf:Description rdf:about=''
|
||||||
|
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||||
|
<digiKam:TagsList>
|
||||||
|
<rdf:Seq>
|
||||||
|
<rdf:li>Kids</rdf:li>
|
||||||
|
<rdf:li>Suzy</rdf:li>
|
||||||
|
<rdf:li>Katie</rdf:li>
|
||||||
|
</rdf:Seq>
|
||||||
|
</digiKam:TagsList>
|
||||||
|
</rdf:Description>
|
||||||
|
<rdf:Description rdf:about=''
|
||||||
|
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||||
|
<xmp:CreateDate>2018-09-28T15:35:49</xmp:CreateDate>
|
||||||
|
<xmp:ModifyDate>2018-09-28T15:35:49</xmp:ModifyDate>
|
||||||
|
</rdf:Description>
|
||||||
|
</rdf:RDF>
|
||||||
|
</x:xmpmeta>"""
|
||||||
|
|
||||||
|
xmp_expected_lines = [line.strip() for line in xmp_expected.split("\n")]
|
||||||
|
|
||||||
|
xmp_got = photos[0]._xmp_sidecar()
|
||||||
|
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):
|
||||||
|
assert line_expected == line_got
|
||||||
|
|
||||||
|
def test_xmp_sidecar_use_albums_keyword():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
photosdb.use_albums_as_keywords = True
|
||||||
|
photos = photosdb.photos(uuid=[UUID_DICT["xmp"]])
|
||||||
|
|
||||||
|
xmp_expected = """<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||||
|
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||||
|
<!-- mirrors Photos 5 "Export IPTC as XMP" option -->
|
||||||
|
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||||
|
<rdf:Description rdf:about=""
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||||
|
<dc:description>Girls with pumpkins</dc:description>
|
||||||
|
<dc:title>Can we carry this?</dc:title>
|
||||||
|
<!-- keywords and persons listed in <dc:subject> as Photos does -->
|
||||||
|
<dc:subject>
|
||||||
|
<rdf:Seq>
|
||||||
|
<rdf:li>Kids</rdf:li>
|
||||||
|
<rdf:li>Suzy</rdf:li>
|
||||||
|
<rdf:li>Katie</rdf:li>
|
||||||
|
</rdf:Seq>
|
||||||
|
</dc:subject>
|
||||||
|
<photoshop:DateCreated>2018-09-28T15:35:49.063000-04:00</photoshop:DateCreated>
|
||||||
|
</rdf:Description>
|
||||||
|
<rdf:Description rdf:about=''
|
||||||
|
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||||
|
<Iptc4xmpExt:PersonInImage>
|
||||||
|
<rdf:Bag>
|
||||||
|
<rdf:li>Suzy</rdf:li>
|
||||||
|
<rdf:li>Katie</rdf:li>
|
||||||
|
</rdf:Bag>
|
||||||
|
</Iptc4xmpExt:PersonInImage>
|
||||||
|
</rdf:Description>
|
||||||
|
<rdf:Description rdf:about=''
|
||||||
|
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||||
|
<digiKam:TagsList>
|
||||||
|
<rdf:Seq>
|
||||||
|
<rdf:li>Kids</rdf:li>
|
||||||
|
<rdf:li>Pumpkin Farm</rdf:li>
|
||||||
|
<rdf:li>Test Album</rdf:li>
|
||||||
|
</rdf:Seq>
|
||||||
|
</digiKam:TagsList>
|
||||||
|
</rdf:Description>
|
||||||
|
<rdf:Description rdf:about=''
|
||||||
|
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||||
|
<xmp:CreateDate>2018-09-28T15:35:49</xmp:CreateDate>
|
||||||
|
<xmp:ModifyDate>2018-09-28T15:35:49</xmp:ModifyDate>
|
||||||
|
</rdf:Description>
|
||||||
|
</rdf:RDF>
|
||||||
|
</x:xmpmeta>"""
|
||||||
|
|
||||||
|
xmp_expected_lines = [line.strip() for line in xmp_expected.split("\n")]
|
||||||
|
|
||||||
|
xmp_got = photos[0]._xmp_sidecar()
|
||||||
|
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):
|
||||||
|
assert line_expected == line_got
|
||||||
Reference in New Issue
Block a user