Feature metadata changed 621 (#684)
* Added metadata_changed to ExportResults, #621 * Updated docs
This commit is contained in:
@@ -2981,6 +2981,9 @@ Returns a [ScoreInfo](#scoreinfo) data class object which provides access to the
|
||||
|
||||
Returns list of PhotoInfo objects for *possible* duplicates or empty list if no matching duplicates. Photos are considered possible duplicates if the photo's original file size, date created, height, and width match another those of another photo. This does not do a byte-for-byte comparison or compute a hash which makes it fast and allows for identification of possible duplicates even if originals are not downloaded from iCloud. The signature-based approach should be robust enough to match duplicates created either through the "duplicate photo" menu item or imported twice into the library but you should not rely on this 100% for identification of all duplicates.
|
||||
|
||||
#### `hexdigest`
|
||||
Returns a unique digest of the photo's properties and metadata; useful for detecting changes in any property/metadata of the photo.
|
||||
|
||||
#### `json()`
|
||||
|
||||
Returns a JSON representation of all photo info.
|
||||
@@ -4174,6 +4177,7 @@ Attributes:
|
||||
* exiftool_error: list of errors generated by exiftool during export
|
||||
* xattr_written: list of files with extended attributes written during export
|
||||
* xattr_skipped: list of files where extended attributes were skipped when update=True
|
||||
* metadata_changed: list of files where metadata changed since last export
|
||||
* deleted_files: reserved for use by osxphotos CLI
|
||||
* deleted_directories: reserved for use by osxphotos CLI
|
||||
* exported_album: reserved for use by osxphotos CLI
|
||||
|
||||
@@ -50,6 +50,7 @@ def post_function(
|
||||
# exported_album: list of tuples of (filename, album_name) for exported files added to album with --add-exported-to-album
|
||||
# skipped_album: list of tuples of (filename, album_name) for skipped files added to album with --add-skipped-to-album
|
||||
# missing_album: list of tuples of (filename, album_name) for missing files added to album with --add-missing-to-album
|
||||
# metadata_changed: list of filenames that had metadata changes since last export
|
||||
|
||||
for filename in results.exported:
|
||||
# do your processing here
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import tempfile
|
||||
import typing as t
|
||||
from collections import namedtuple # pylint: disable=syntax-error
|
||||
from dataclasses import asdict, dataclass
|
||||
@@ -45,14 +43,13 @@ from .photokit import (
|
||||
from .phototemplate import RenderOptions
|
||||
from .rich_utils import add_rich_markup_tag
|
||||
from .uti import get_preferred_uti_extension
|
||||
from .utils import increment_filename, lineno, list_directory
|
||||
from .utils import hexdigest, increment_filename, lineno, list_directory
|
||||
|
||||
__all__ = [
|
||||
"ExportError",
|
||||
"ExportOptions",
|
||||
"ExportResults",
|
||||
"PhotoExporter",
|
||||
"hexdigest",
|
||||
"rename_jpeg_files",
|
||||
]
|
||||
|
||||
@@ -266,6 +263,7 @@ class ExportResults:
|
||||
exported_album=None,
|
||||
skipped_album=None,
|
||||
missing_album=None,
|
||||
metadata_changed=None,
|
||||
):
|
||||
self.exported = exported or []
|
||||
self.new = new or []
|
||||
@@ -292,6 +290,7 @@ class ExportResults:
|
||||
self.exported_album = exported_album or []
|
||||
self.skipped_album = skipped_album or []
|
||||
self.missing_album = missing_album or []
|
||||
self.metadata_changed = metadata_changed or []
|
||||
|
||||
def all_files(self):
|
||||
"""return all filenames contained in results"""
|
||||
@@ -342,6 +341,7 @@ class ExportResults:
|
||||
self.exported_album += other.exported_album
|
||||
self.skipped_album += other.skipped_album
|
||||
self.missing_album += other.missing_album
|
||||
self.metadata_changed += other.metadata_changed
|
||||
|
||||
return self
|
||||
|
||||
@@ -371,12 +371,14 @@ class ExportResults:
|
||||
+ f",exported_album={self.exported_album}"
|
||||
+ f",skipped_album={self.skipped_album}"
|
||||
+ f",missing_album={self.missing_album}"
|
||||
+ f",metadata_changed={self.metadata_changed}"
|
||||
+ ")"
|
||||
)
|
||||
|
||||
|
||||
class PhotoExporter:
|
||||
"""Export a photo"""
|
||||
|
||||
def __init__(self, photo: "PhotoInfo", tmpdir: t.Optional[str] = None):
|
||||
self.photo = photo
|
||||
self._render_options = RenderOptions()
|
||||
@@ -739,7 +741,7 @@ class PhotoExporter:
|
||||
return ShouldUpdate.EDITED_SIG_DIFFERENT
|
||||
|
||||
if options.force_update:
|
||||
current_digest = hexdigest(self.photo.json())
|
||||
current_digest = self.photo.hexdigest
|
||||
if current_digest != file_record.digest:
|
||||
# metadata in Photos changed, force update
|
||||
return ShouldUpdate.DIGEST_DIFFERENT
|
||||
@@ -1179,8 +1181,9 @@ class PhotoExporter:
|
||||
rec.dest_sig = fileutil.file_sig(dest)
|
||||
if options.exiftool:
|
||||
rec.exifdata = self._exiftool_json_sidecar(options)
|
||||
if options.force_update:
|
||||
rec.digest = hexdigest(photoinfo)
|
||||
if self.photo.hexdigest != rec.digest:
|
||||
results.metadata_changed = [dest_str]
|
||||
rec.digest = self.photo.hexdigest
|
||||
|
||||
return results
|
||||
|
||||
@@ -2011,13 +2014,6 @@ class PhotoExporter:
|
||||
f.close()
|
||||
|
||||
|
||||
def hexdigest(strval):
|
||||
"""hexdigest of a string, using blake2b"""
|
||||
h = hashlib.blake2b(digest_size=20)
|
||||
h.update(bytes(strval, "utf-8"))
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def _check_export_suffix(src, dest, edited):
|
||||
"""Helper function for exporting photos to check file extensions of destination path.
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import os
|
||||
import os.path
|
||||
import pathlib
|
||||
from datetime import timedelta, timezone
|
||||
from functools import cached_property
|
||||
from typing import Optional
|
||||
|
||||
import yaml
|
||||
@@ -54,7 +55,7 @@ from .scoreinfo import ScoreInfo
|
||||
from .searchinfo import SearchInfo
|
||||
from .text_detection import detect_text
|
||||
from .uti import get_preferred_uti_extension, get_uti_for_extension
|
||||
from .utils import _get_resource_loc, list_directory
|
||||
from .utils import _get_resource_loc, hexdigest, list_directory
|
||||
|
||||
__all__ = ["PhotoInfo", "PhotoInfoNone"]
|
||||
|
||||
@@ -1356,6 +1357,12 @@ class PhotoInfo:
|
||||
self._exiftool = exiftool
|
||||
return self._exiftool
|
||||
|
||||
@cached_property
|
||||
def hexdigest(self):
|
||||
"""Returns a unique digest of the photo's properties and metadata;
|
||||
useful for detecting changes in any property/metadata of the photo"""
|
||||
return hexdigest(self.json())
|
||||
|
||||
def detected_text(self, confidence_threshold=TEXT_DETECTION_CONFIDENCE_THRESHOLD):
|
||||
"""Detects text in photo and returns lists of results as (detected text, confidence)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import datetime
|
||||
import fnmatch
|
||||
import glob
|
||||
import hashlib
|
||||
import importlib
|
||||
import inspect
|
||||
import logging
|
||||
@@ -29,6 +30,7 @@ __all__ = [
|
||||
"expand_and_validate_filepath",
|
||||
"get_last_library_path",
|
||||
"get_system_library_path",
|
||||
"hexdigest",
|
||||
"increment_filename_with_count",
|
||||
"increment_filename",
|
||||
"lineno",
|
||||
@@ -517,3 +519,10 @@ def get_latest_version() -> Tuple[Optional[str], str]:
|
||||
def pluralize(count, singular, plural):
|
||||
"""Return singular or plural based on count"""
|
||||
return singular if count == 1 else plural
|
||||
|
||||
|
||||
def hexdigest(strval: str) -> str:
|
||||
"""hexdigest of a string, using blake2b"""
|
||||
h = hashlib.blake2b(digest_size=20)
|
||||
h.update(bytes(strval, "utf-8"))
|
||||
return h.hexdigest()
|
||||
|
||||
Reference in New Issue
Block a user