Refactor update osxmetadata (#804)
* Updated osxmetadata to use v1.0.0 * Added README_DEV * fix for missing detected_text xattr * fix for missing detected_text xattr
This commit is contained in:
parent
0ba8bc3eb9
commit
5665cf1804
@ -2204,9 +2204,9 @@ Attributes:
|
|||||||
|
|
||||||
### <a name="textdetection">Text Detection</a>
|
### <a name="textdetection">Text Detection</a>
|
||||||
|
|
||||||
The [PhotoInfo.detected_text()](#detected_text_method) and the `{detected_text}` template will perform text detection on the photos in your library. Text detection is a slow process so to avoid unnecessary re-processing of photos, osxphotos will cache the results of the text detection process as an extended attribute on the photo image file. Extended attributes do not modify the actual file. The extended attribute is named `osxphotos.metadata:detected_text` and can be viewed using the built-in [xattr](https://ss64.com/osx/xattr.html) command or my [osxmetadata](https://github.com/RhetTbull/osxmetadata) tool. If you want to remove the cached attribute, you can do so with osxmetadata as follows:
|
The [PhotoInfo.detected_text()](#detected_text_method) and the `{detected_text}` template will perform text detection on the photos in your library. Text detection is a slow process so to avoid unnecessary re-processing of photos, osxphotos will cache the results of the text detection process as an extended attribute on the photo image file. Extended attributes do not modify the actual file. The extended attribute is named `osxphotos.metadata:detected_text` and can be viewed using the built-in [xattr](https://ss64.com/osx/xattr.html) command or my [osxmetadata](https://github.com/RhetTbull/osxmetadata) tool. If you want to remove the cached attribute, you can do so with `xattr` as follows:
|
||||||
|
|
||||||
`osxmetadata --clear osxphotos.metadata:detected_text --walk ~/Pictures/Photos\ Library.photoslibrary/`
|
`find ~/Pictures/Photos\ Library.photoslibrary | xargs -I{} xattr -c osxphotos.metadata:detected_text '{}'`
|
||||||
|
|
||||||
### Utility Functions
|
### Utility Functions
|
||||||
|
|
||||||
|
|||||||
25
README_DEV.md
Normal file
25
README_DEV.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Developer Notes for osxphotos
|
||||||
|
|
||||||
|
These are notes for developers working on osxphotos. They're mostly to help me remember how to do things in this repo but will be useful to anyone who wants to contribute to osxphotos.
|
||||||
|
|
||||||
|
## Installing osxphotos
|
||||||
|
|
||||||
|
- Clone the repo: `git clone git@github.com:RhetTbull/osxphotos.git`
|
||||||
|
- Create a virtual environment and activate it: `python3 -m venv venv` then `source venv/bin/activate`. I use [pyenv](https://github.com/pyenv/pyenv) with [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv) to manage my virtual environments
|
||||||
|
- Install the requirements: `pip install -r requirements.txt`
|
||||||
|
- Install the development requirements: `pip install -r requirements-dev.txt`
|
||||||
|
- Install osxphotos: `pip install -e .`
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
|
- Run all tests: `pytest`
|
||||||
|
|
||||||
|
See the [test README.md](tests/README.md) for more information on running tests.
|
||||||
|
|
||||||
|
## Building the package
|
||||||
|
|
||||||
|
- Run `./build.sh` to run the build script.
|
||||||
|
|
||||||
|
## Other Notes
|
||||||
|
|
||||||
|
[cogapp](https://nedbatchelder.com/code/cog/index.html) is used to update the README.md and other files. cog will be called from the build script as needed.
|
||||||
@ -262,10 +262,9 @@ EXTENDED_ATTRIBUTE_NAMES = [
|
|||||||
"description",
|
"description",
|
||||||
"findercomment",
|
"findercomment",
|
||||||
"headline",
|
"headline",
|
||||||
"keywords",
|
|
||||||
"participants",
|
"participants",
|
||||||
"projects",
|
"projects",
|
||||||
"rating",
|
"starrating",
|
||||||
"subject",
|
"subject",
|
||||||
"title",
|
"title",
|
||||||
"version",
|
"version",
|
||||||
|
|||||||
@ -11,10 +11,16 @@ import shlex
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import Iterable, List, Tuple
|
from typing import Iterable, List, Optional, Tuple
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import osxmetadata
|
from osxmetadata import (
|
||||||
|
MDITEM_ATTRIBUTE_DATA,
|
||||||
|
MDITEM_ATTRIBUTE_SHORT_NAMES,
|
||||||
|
OSXMetaData,
|
||||||
|
Tag,
|
||||||
|
)
|
||||||
|
from osxmetadata.constants import _TAGS_NAMES
|
||||||
|
|
||||||
import osxphotos
|
import osxphotos
|
||||||
from osxphotos._constants import (
|
from osxphotos._constants import (
|
||||||
@ -86,7 +92,7 @@ from .common import (
|
|||||||
from .help import ExportCommand, get_help_msg
|
from .help import ExportCommand, get_help_msg
|
||||||
from .list import _list_libraries
|
from .list import _list_libraries
|
||||||
from .param_types import ExportDBType, FunctionCall, TemplateString
|
from .param_types import ExportDBType, FunctionCall, TemplateString
|
||||||
from .report_writer import report_writer_factory, ReportWriterNoOp
|
from .report_writer import ReportWriterNoOp, report_writer_factory
|
||||||
from .rich_progress import rich_progress
|
from .rich_progress import rich_progress
|
||||||
from .verbose import get_verbose_console, time_stamp, verbose_print
|
from .verbose import get_verbose_console, time_stamp, verbose_print
|
||||||
|
|
||||||
@ -2683,9 +2689,9 @@ def write_finder_tags(
|
|||||||
]
|
]
|
||||||
tags.extend(rendered_tags)
|
tags.extend(rendered_tags)
|
||||||
|
|
||||||
tags = [osxmetadata.Tag(tag) for tag in set(tags)]
|
tags = [Tag(tag, 0) for tag in set(tags)]
|
||||||
for f in files:
|
for f in files:
|
||||||
md = osxmetadata.OSXMetaData(f)
|
md = OSXMetaData(f)
|
||||||
if sorted(md.tags) != sorted(tags):
|
if sorted(md.tags) != sorted(tags):
|
||||||
verbose_(f"Writing Finder tags to {f}")
|
verbose_(f"Writing Finder tags to {f}")
|
||||||
md.tags = tags
|
md.tags = tags
|
||||||
@ -2747,24 +2753,24 @@ def write_extended_attributes(
|
|||||||
written = set()
|
written = set()
|
||||||
skipped = set()
|
skipped = set()
|
||||||
for f in files:
|
for f in files:
|
||||||
md = osxmetadata.OSXMetaData(f)
|
md = OSXMetaData(f)
|
||||||
for attr, value in attributes.items():
|
for attr, value in attributes.items():
|
||||||
islist = osxmetadata.ATTRIBUTES[attr].list
|
attr_type = get_metadata_attribute_type(attr) or "str"
|
||||||
if value:
|
if value:
|
||||||
value = ", ".join(value) if not islist else sorted(value)
|
value = sorted(list(value)) if attr_type == "list" else ", ".join(value)
|
||||||
file_value = md.get_attribute(attr)
|
file_value = md.get(attr)
|
||||||
|
|
||||||
if file_value and islist:
|
if file_value and attr_type == "lists":
|
||||||
file_value = sorted(file_value)
|
file_value = sorted(file_value)
|
||||||
|
|
||||||
if (not file_value and not value) or file_value == value:
|
if (not file_value and not value) or file_value == value:
|
||||||
# if both not set or both equal, nothing to do
|
# if both not set or both equal, nothing to do
|
||||||
# get_attribute returns None if not set and value will be [] if not set so can't directly compare
|
# get returns None if not set and value will be [] if not set so can't directly compare
|
||||||
verbose_(f"Skipping extended attribute {attr} for {f}: nothing to do")
|
verbose_(f"Skipping extended attribute {attr} for {f}: nothing to do")
|
||||||
skipped.add(f)
|
skipped.add(f)
|
||||||
else:
|
else:
|
||||||
verbose_(f"Writing extended attribute {attr} to {f}")
|
verbose_(f"Writing extended attribute {attr} to {f}")
|
||||||
md.set_attribute(attr, value)
|
md.set(attr, value)
|
||||||
written.add(f)
|
written.add(f)
|
||||||
|
|
||||||
return list(written), [f for f in skipped if f not in written]
|
return list(written), [f for f in skipped if f not in written]
|
||||||
@ -2841,3 +2847,23 @@ def render_and_validate_report(report: str, exiftool_path: str, export_dir: str)
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
def get_metadata_attribute_type(attr: str) -> Optional[str]:
|
||||||
|
"""Get the type of a metadata attribute
|
||||||
|
|
||||||
|
Args:
|
||||||
|
attr: attribute name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
type of attribute as string or None if type is not known
|
||||||
|
"""
|
||||||
|
if attr in MDITEM_ATTRIBUTE_SHORT_NAMES:
|
||||||
|
attr = MDITEM_ATTRIBUTE_SHORT_NAMES[attr]
|
||||||
|
return (
|
||||||
|
"list"
|
||||||
|
if attr in _TAGS_NAMES
|
||||||
|
else MDITEM_ATTRIBUTE_DATA[attr]["python_type"]
|
||||||
|
if attr in MDITEM_ATTRIBUTE_DATA
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import re
|
|||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import osxmetadata
|
from osxmetadata import MDITEM_ATTRIBUTE_DATA, MDITEM_ATTRIBUTE_SHORT_NAMES
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.markdown import Markdown
|
from rich.markdown import Markdown
|
||||||
|
|
||||||
@ -256,34 +256,46 @@ class ExportCommand(click.Command):
|
|||||||
formatter.write_text(
|
formatter.write_text(
|
||||||
"""
|
"""
|
||||||
Some options (currently '--finder-tag-template', '--finder-tag-keywords', '-xattr-template') write
|
Some options (currently '--finder-tag-template', '--finder-tag-keywords', '-xattr-template') write
|
||||||
additional metadata to extended attributes in the file. These options will only work
|
additional metadata accessible by Spotlight to facilitate searching.
|
||||||
if the destination filesystem supports extended attributes (most do).
|
|
||||||
For example, --finder-tag-keyword writes all keywords (including any specified by '--keyword-template'
|
For example, --finder-tag-keyword writes all keywords (including any specified by '--keyword-template'
|
||||||
or other options) to Finder tags that are searchable in Spotlight using the syntax: 'tag:tagname'.
|
or other options) to Finder tags that are searchable in Spotlight using the syntax: 'tag:tagname'.
|
||||||
For example, if you have images with keyword "Travel" then using '--finder-tag-keywords' you could quickly
|
For example, if you have images with keyword "Travel" then using '--finder-tag-keywords' you could quickly
|
||||||
find those images in the Finder by typing 'tag:Travel' in the Spotlight search bar.
|
find those images in the Finder by typing 'tag:Travel' in the Spotlight search bar.
|
||||||
Finder tags are written to the 'com.apple.metadata:_kMDItemUserTags' extended attribute.
|
Finder tags are written to the 'com.apple.metadata:_kMDItemUserTags' extended attribute.
|
||||||
Unlike EXIF metadata, extended attributes do not modify the actual file. Most cloud storage services
|
Unlike EXIF metadata, extended attributes do not modify the actual file;
|
||||||
do not synch extended attributes. Dropbox does sync them and any changes to a file's extended attributes
|
the metadata is written to extended attributes associated with the file and the Spotlight metadata database.
|
||||||
|
Most cloud storage services do not synch extended attributes.
|
||||||
|
Dropbox does sync them and any changes to a file's extended attributes
|
||||||
will cause Dropbox to re-sync the files.
|
will cause Dropbox to re-sync the files.
|
||||||
|
|
||||||
The following attributes may be used with '--xattr-template':
|
The following attributes may be used with '--xattr-template':
|
||||||
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# build help text from all the attribute names
|
||||||
|
# passed to click.HelpFormatter.write_dl for formatting
|
||||||
attr_tuples = [
|
attr_tuples = [
|
||||||
(
|
(
|
||||||
rich_text("[bold]Attribute[/bold]", width=formatter.width),
|
rich_text("[bold]Attribute[/bold]", width=formatter.width),
|
||||||
rich_text("[bold]Description[/bold]", width=formatter.width),
|
rich_text("[bold]Description[/bold]", width=formatter.width),
|
||||||
),
|
)
|
||||||
*[
|
|
||||||
(
|
|
||||||
attr,
|
|
||||||
f"{osxmetadata.ATTRIBUTES[attr].help} ({osxmetadata.ATTRIBUTES[attr].constant})",
|
|
||||||
)
|
|
||||||
for attr in EXTENDED_ATTRIBUTE_NAMES
|
|
||||||
],
|
|
||||||
]
|
]
|
||||||
|
for attr_key in sorted(EXTENDED_ATTRIBUTE_NAMES):
|
||||||
|
# get short and long name
|
||||||
|
attr = MDITEM_ATTRIBUTE_SHORT_NAMES[attr_key]
|
||||||
|
short_name = MDITEM_ATTRIBUTE_DATA[attr]["short_name"]
|
||||||
|
long_name = MDITEM_ATTRIBUTE_DATA[attr]["name"]
|
||||||
|
constant = MDITEM_ATTRIBUTE_DATA[attr]["xattr_constant"]
|
||||||
|
|
||||||
|
# get help text
|
||||||
|
description = MDITEM_ATTRIBUTE_DATA[attr]["description"]
|
||||||
|
type_ = MDITEM_ATTRIBUTE_DATA[attr]["help_type"]
|
||||||
|
attr_help = f"{long_name}; {constant}; {description}; {type_}"
|
||||||
|
|
||||||
|
# add to list
|
||||||
|
attr_tuples.append((short_name, attr_help))
|
||||||
|
|
||||||
formatter.write_dl(attr_tuples)
|
formatter.write_dl(attr_tuples)
|
||||||
formatter.write("\n")
|
formatter.write("\n")
|
||||||
formatter.write_text(
|
formatter.write_text(
|
||||||
|
|||||||
@ -1440,11 +1440,29 @@ class PhotoInfo:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
md = OSXMetaData(path)
|
md = OSXMetaData(path)
|
||||||
detected_text = md.get_attribute("osxphotos_detected_text")
|
try:
|
||||||
|
|
||||||
|
def decoder(val):
|
||||||
|
"""Decode value from JSON"""
|
||||||
|
return json.loads(val.decode("utf-8"))
|
||||||
|
|
||||||
|
detected_text = md.get_xattr(
|
||||||
|
"osxphotos.metadata:detected_text", decode=decoder
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
detected_text = None
|
||||||
if detected_text is None:
|
if detected_text is None:
|
||||||
orientation = self.orientation or None
|
orientation = self.orientation or None
|
||||||
detected_text = detect_text(path, orientation)
|
detected_text = detect_text(path, orientation)
|
||||||
md.set_attribute("osxphotos_detected_text", detected_text)
|
|
||||||
|
def encoder(obj):
|
||||||
|
"""Encode value as JSON"""
|
||||||
|
val = json.dumps(obj)
|
||||||
|
return val.encode("utf-8")
|
||||||
|
|
||||||
|
md.set_xattr(
|
||||||
|
"osxphotos.metadata:detected_text", detected_text, encode=encoder
|
||||||
|
)
|
||||||
return detected_text
|
return detected_text
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@ -5,10 +5,10 @@ bitmath>=1.3.3.1,<1.4.0.0
|
|||||||
bpylist2==4.0.1
|
bpylist2==4.0.1
|
||||||
more-itertools>=8.8.0,<9.0.0
|
more-itertools>=8.8.0,<9.0.0
|
||||||
objexplore>=1.6.3,<2.0.0
|
objexplore>=1.6.3,<2.0.0
|
||||||
osxmetadata>=0.99.34,<1.0.0
|
osxmetadata>=<1.0.0,<2.0.0
|
||||||
packaging>=21.3
|
packaging>=21.3
|
||||||
pathvalidate>=2.4.1,<2.5.0
|
pathvalidate>=2.4.1,<2.5.0
|
||||||
photoscript>=0.1.4,<0.2.0
|
photoscript>=0.1.6,<0.2.0
|
||||||
ptpython>=3.0.20,<3.1.0
|
ptpython>=3.0.20,<3.1.0
|
||||||
pyobjc-core>=7.3,<9.0
|
pyobjc-core>=7.3,<9.0
|
||||||
pyobjc-framework-AVFoundation>=7.3,<9.0
|
pyobjc-framework-AVFoundation>=7.3,<9.0
|
||||||
|
|||||||
4
setup.py
4
setup.py
@ -80,10 +80,10 @@ setup(
|
|||||||
"bpylist2==4.0.1",
|
"bpylist2==4.0.1",
|
||||||
"more-itertools>=8.8.0,<9.0.0",
|
"more-itertools>=8.8.0,<9.0.0",
|
||||||
"objexplore>=1.6.3,<2.0.0",
|
"objexplore>=1.6.3,<2.0.0",
|
||||||
"osxmetadata>=0.99.34,<1.0.0",
|
"osxmetadata>=1.0.0,<2.0.0",
|
||||||
"packaging>=21.3",
|
"packaging>=21.3",
|
||||||
"pathvalidate>=2.4.1,<3.0.0",
|
"pathvalidate>=2.4.1,<3.0.0",
|
||||||
"photoscript>=0.1.4,<0.2.0",
|
"photoscript>=0.1.6,<0.2.0",
|
||||||
"ptpython>=3.0.20,<4.0.0",
|
"ptpython>=3.0.20,<4.0.0",
|
||||||
"pyobjc-core>=7.3,<9.0",
|
"pyobjc-core>=7.3,<9.0",
|
||||||
"pyobjc-framework-AVFoundation>=7.3,<9.0",
|
"pyobjc-framework-AVFoundation>=7.3,<9.0",
|
||||||
|
|||||||
@ -6514,7 +6514,7 @@ def test_export_finder_tag_keywords():
|
|||||||
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
||||||
keywords = CLI_FINDER_TAGS[uuid]["IPTC:Keywords"]
|
keywords = CLI_FINDER_TAGS[uuid]["IPTC:Keywords"]
|
||||||
keywords = [keywords] if type(keywords) != list else keywords
|
keywords = [keywords] if type(keywords) != list else keywords
|
||||||
expected = [Tag(x) for x in keywords]
|
expected = [Tag(x, 0) for x in keywords]
|
||||||
assert sorted(md.tags) == sorted(expected)
|
assert sorted(md.tags) == sorted(expected)
|
||||||
|
|
||||||
# run again with --update, should skip writing extended attributes
|
# run again with --update, should skip writing extended attributes
|
||||||
@ -6536,7 +6536,7 @@ def test_export_finder_tag_keywords():
|
|||||||
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
||||||
keywords = CLI_FINDER_TAGS[uuid]["IPTC:Keywords"]
|
keywords = CLI_FINDER_TAGS[uuid]["IPTC:Keywords"]
|
||||||
keywords = [keywords] if type(keywords) != list else keywords
|
keywords = [keywords] if type(keywords) != list else keywords
|
||||||
expected = [Tag(x) for x in keywords]
|
expected = [Tag(x, 0) for x in keywords]
|
||||||
assert sorted(md.tags) == sorted(expected)
|
assert sorted(md.tags) == sorted(expected)
|
||||||
|
|
||||||
# clear tags and run again, should update extended attributes
|
# clear tags and run again, should update extended attributes
|
||||||
@ -6560,7 +6560,7 @@ def test_export_finder_tag_keywords():
|
|||||||
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
||||||
keywords = CLI_FINDER_TAGS[uuid]["IPTC:Keywords"]
|
keywords = CLI_FINDER_TAGS[uuid]["IPTC:Keywords"]
|
||||||
keywords = [keywords] if type(keywords) != list else keywords
|
keywords = [keywords] if type(keywords) != list else keywords
|
||||||
expected = [Tag(x) for x in keywords]
|
expected = [Tag(x, 0) for x in keywords]
|
||||||
assert sorted(md.tags) == sorted(expected)
|
assert sorted(md.tags) == sorted(expected)
|
||||||
|
|
||||||
|
|
||||||
@ -6589,7 +6589,7 @@ def test_export_finder_tag_template():
|
|||||||
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
||||||
keywords = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
|
keywords = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
|
||||||
keywords = [keywords] if type(keywords) != list else keywords
|
keywords = [keywords] if type(keywords) != list else keywords
|
||||||
expected = [Tag(x) for x in keywords]
|
expected = [Tag(x, 0) for x in keywords]
|
||||||
assert sorted(md.tags) == sorted(expected)
|
assert sorted(md.tags) == sorted(expected)
|
||||||
|
|
||||||
# run again with --update, should skip writing extended attributes
|
# run again with --update, should skip writing extended attributes
|
||||||
@ -6612,7 +6612,7 @@ def test_export_finder_tag_template():
|
|||||||
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
||||||
keywords = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
|
keywords = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
|
||||||
keywords = [keywords] if type(keywords) != list else keywords
|
keywords = [keywords] if type(keywords) != list else keywords
|
||||||
expected = [Tag(x) for x in keywords]
|
expected = [Tag(x, 0) for x in keywords]
|
||||||
assert sorted(md.tags) == sorted(expected)
|
assert sorted(md.tags) == sorted(expected)
|
||||||
|
|
||||||
# clear tags and run again, should update extended attributes
|
# clear tags and run again, should update extended attributes
|
||||||
@ -6637,7 +6637,7 @@ def test_export_finder_tag_template():
|
|||||||
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
||||||
keywords = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
|
keywords = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
|
||||||
keywords = [keywords] if type(keywords) != list else keywords
|
keywords = [keywords] if type(keywords) != list else keywords
|
||||||
expected = [Tag(x) for x in keywords]
|
expected = [Tag(x, 0) for x in keywords]
|
||||||
assert sorted(md.tags) == sorted(expected)
|
assert sorted(md.tags) == sorted(expected)
|
||||||
|
|
||||||
|
|
||||||
@ -6670,7 +6670,7 @@ def test_export_finder_tag_template_multiple():
|
|||||||
keywords = [keywords] if type(keywords) != list else keywords
|
keywords = [keywords] if type(keywords) != list else keywords
|
||||||
persons = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
|
persons = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
|
||||||
persons = [persons] if type(persons) != list else persons
|
persons = [persons] if type(persons) != list else persons
|
||||||
expected = [Tag(x) for x in set(keywords + persons)]
|
expected = [Tag(x, 0) for x in set(keywords + persons)]
|
||||||
assert sorted(md.tags) == sorted(expected)
|
assert sorted(md.tags) == sorted(expected)
|
||||||
|
|
||||||
|
|
||||||
@ -6702,7 +6702,7 @@ def test_export_finder_tag_template_keywords():
|
|||||||
keywords = [keywords] if type(keywords) != list else keywords
|
keywords = [keywords] if type(keywords) != list else keywords
|
||||||
persons = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
|
persons = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
|
||||||
persons = [persons] if type(persons) != list else persons
|
persons = [persons] if type(persons) != list else persons
|
||||||
expected = [Tag(x) for x in set(keywords + persons)]
|
expected = [Tag(x, 0) for x in set(keywords + persons)]
|
||||||
assert sorted(md.tags) == sorted(expected)
|
assert sorted(md.tags) == sorted(expected)
|
||||||
|
|
||||||
|
|
||||||
@ -6731,17 +6731,23 @@ def test_export_finder_tag_template_multi_field():
|
|||||||
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
||||||
title = CLI_FINDER_TAGS[uuid]["XMP:Title"] or ""
|
title = CLI_FINDER_TAGS[uuid]["XMP:Title"] or ""
|
||||||
descr = CLI_FINDER_TAGS[uuid]["XMP:Description"] or ""
|
descr = CLI_FINDER_TAGS[uuid]["XMP:Description"] or ""
|
||||||
expected = [Tag(f"{title};{descr}")]
|
expected = [Tag(f"{title};{descr}", 0)]
|
||||||
assert sorted(md.tags) == sorted(expected)
|
assert sorted(md.tags) == sorted(expected)
|
||||||
|
|
||||||
|
|
||||||
def test_export_xattr_template():
|
def test_export_xattr_template():
|
||||||
"""test --xattr template"""
|
"""test --xattr template"""
|
||||||
|
|
||||||
|
# Note: this test does not actually test that the metadata attributes get correctly
|
||||||
|
# written by osxmetadata as osxmetadata doesn't work reliably when run by pytest
|
||||||
|
# (but does appear to work correctly in practice)
|
||||||
|
# Reference: https://github.com/RhetTbull/osxmetadata/issues/68
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
# pylint: disable=not-context-manager
|
# pylint: disable=not-context-manager
|
||||||
with runner.isolated_filesystem():
|
with runner.isolated_filesystem():
|
||||||
|
test_dir = os.getcwd()
|
||||||
for uuid in CLI_FINDER_TAGS:
|
for uuid in CLI_FINDER_TAGS:
|
||||||
result = runner.invoke(
|
result = runner.invoke(
|
||||||
export,
|
export,
|
||||||
@ -6750,8 +6756,8 @@ def test_export_xattr_template():
|
|||||||
".",
|
".",
|
||||||
"-V",
|
"-V",
|
||||||
"--xattr-template",
|
"--xattr-template",
|
||||||
"keywords",
|
"copyright",
|
||||||
"{person}",
|
"osxphotos 2022",
|
||||||
"--xattr-template",
|
"--xattr-template",
|
||||||
"comment",
|
"comment",
|
||||||
"{title};{descr}",
|
"{title};{descr}",
|
||||||
@ -6760,14 +6766,8 @@ def test_export_xattr_template():
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
|
assert "Writing extended attribute copyright" in result.output
|
||||||
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
assert "Writing extended attribute comment" in result.output
|
||||||
expected = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
|
|
||||||
expected = [expected] if type(expected) != list else expected
|
|
||||||
assert sorted(md.keywords) == sorted(expected)
|
|
||||||
title = CLI_FINDER_TAGS[uuid]["XMP:Title"] or ""
|
|
||||||
descr = CLI_FINDER_TAGS[uuid]["XMP:Description"] or ""
|
|
||||||
assert md.comment == f"{title};{descr}"
|
|
||||||
|
|
||||||
# run again with --update, should skip writing extended attributes
|
# run again with --update, should skip writing extended attributes
|
||||||
result = runner.invoke(
|
result = runner.invoke(
|
||||||
@ -6777,8 +6777,8 @@ def test_export_xattr_template():
|
|||||||
".",
|
".",
|
||||||
"-V",
|
"-V",
|
||||||
"--xattr-template",
|
"--xattr-template",
|
||||||
"keywords",
|
"copyright",
|
||||||
"{person}",
|
"osxphotos 2022",
|
||||||
"--xattr-template",
|
"--xattr-template",
|
||||||
"comment",
|
"comment",
|
||||||
"{title};{descr}",
|
"{title};{descr}",
|
||||||
@ -6788,11 +6788,12 @@ def test_export_xattr_template():
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "Skipping extended attribute keywords" in result.output
|
|
||||||
assert "Skipping extended attribute comment" in result.output
|
|
||||||
|
|
||||||
# clear tags and run again, should update extended attributes
|
# clear tags and run again, should update extended attributes
|
||||||
md.keywords = None
|
md = OSXMetaData(
|
||||||
|
os.path.join(test_dir, CLI_FINDER_TAGS[uuid]["File:FileName"])
|
||||||
|
)
|
||||||
|
md.copyright = None
|
||||||
md.comment = None
|
md.comment = None
|
||||||
|
|
||||||
result = runner.invoke(
|
result = runner.invoke(
|
||||||
@ -6802,8 +6803,8 @@ def test_export_xattr_template():
|
|||||||
".",
|
".",
|
||||||
"-V",
|
"-V",
|
||||||
"--xattr-template",
|
"--xattr-template",
|
||||||
"keywords",
|
"copyright",
|
||||||
"{person}",
|
"osxphotos 2022",
|
||||||
"--xattr-template",
|
"--xattr-template",
|
||||||
"comment",
|
"comment",
|
||||||
"{title}",
|
"{title}",
|
||||||
@ -6813,14 +6814,6 @@ def test_export_xattr_template():
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "Writing extended attribute keyword" in result.output
|
|
||||||
assert "Writing extended attribute comment" in result.output
|
|
||||||
|
|
||||||
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
|
|
||||||
expected = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
|
|
||||||
expected = [expected] if type(expected) != list else expected
|
|
||||||
assert sorted(md.keywords) == sorted(expected)
|
|
||||||
assert md.comment == CLI_FINDER_TAGS[uuid]["XMP:Title"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_export_jpeg_ext():
|
def test_export_jpeg_ext():
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user