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:
Rhet Turnbull
2022-10-15 22:12:11 -07:00
committed by GitHub
parent 0ba8bc3eb9
commit 5665cf1804
9 changed files with 142 additions and 69 deletions

View File

@@ -262,10 +262,9 @@ EXTENDED_ATTRIBUTE_NAMES = [
"description",
"findercomment",
"headline",
"keywords",
"participants",
"projects",
"rating",
"starrating",
"subject",
"title",
"version",

View File

@@ -11,10 +11,16 @@ import shlex
import subprocess
import sys
import time
from typing import Iterable, List, Tuple
from typing import Iterable, List, Optional, Tuple
import click
import osxmetadata
from osxmetadata import (
MDITEM_ATTRIBUTE_DATA,
MDITEM_ATTRIBUTE_SHORT_NAMES,
OSXMetaData,
Tag,
)
from osxmetadata.constants import _TAGS_NAMES
import osxphotos
from osxphotos._constants import (
@@ -86,7 +92,7 @@ from .common import (
from .help import ExportCommand, get_help_msg
from .list import _list_libraries
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 .verbose import get_verbose_console, time_stamp, verbose_print
@@ -2683,9 +2689,9 @@ def write_finder_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:
md = osxmetadata.OSXMetaData(f)
md = OSXMetaData(f)
if sorted(md.tags) != sorted(tags):
verbose_(f"Writing Finder tags to {f}")
md.tags = tags
@@ -2747,24 +2753,24 @@ def write_extended_attributes(
written = set()
skipped = set()
for f in files:
md = osxmetadata.OSXMetaData(f)
md = OSXMetaData(f)
for attr, value in attributes.items():
islist = osxmetadata.ATTRIBUTES[attr].list
attr_type = get_metadata_attribute_type(attr) or "str"
if value:
value = ", ".join(value) if not islist else sorted(value)
file_value = md.get_attribute(attr)
value = sorted(list(value)) if attr_type == "list" else ", ".join(value)
file_value = md.get(attr)
if file_value and islist:
if file_value and attr_type == "lists":
file_value = sorted(file_value)
if (not file_value and not value) or file_value == value:
# 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")
skipped.add(f)
else:
verbose_(f"Writing extended attribute {attr} to {f}")
md.set_attribute(attr, value)
md.set(attr, value)
written.add(f)
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)
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
)

View File

@@ -5,7 +5,7 @@ import re
import typing as t
import click
import osxmetadata
from osxmetadata import MDITEM_ATTRIBUTE_DATA, MDITEM_ATTRIBUTE_SHORT_NAMES
from rich.console import Console
from rich.markdown import Markdown
@@ -256,34 +256,46 @@ class ExportCommand(click.Command):
formatter.write_text(
"""
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
if the destination filesystem supports extended attributes (most do).
additional metadata accessible by Spotlight to facilitate searching.
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'.
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.
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
do not synch extended attributes. Dropbox does sync them and any changes to a file's extended attributes
Unlike EXIF metadata, extended attributes do not modify the actual file;
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.
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 = [
(
rich_text("[bold]Attribute[/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("\n")
formatter.write_text(

View File

@@ -1440,11 +1440,29 @@ class PhotoInfo:
return []
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:
orientation = self.orientation or None
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
@property