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.
|
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()`
|
#### `json()`
|
||||||
|
|
||||||
Returns a JSON representation of all photo info.
|
Returns a JSON representation of all photo info.
|
||||||
@@ -4174,6 +4177,7 @@ Attributes:
|
|||||||
* exiftool_error: list of errors generated by exiftool during export
|
* exiftool_error: list of errors generated by exiftool during export
|
||||||
* xattr_written: list of files with extended attributes written 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
|
* 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_files: reserved for use by osxphotos CLI
|
||||||
* deleted_directories: reserved for use by osxphotos CLI
|
* deleted_directories: reserved for use by osxphotos CLI
|
||||||
* exported_album: 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
|
# 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
|
# 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
|
# 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:
|
for filename in results.exported:
|
||||||
# do your processing here
|
# do your processing here
|
||||||
|
|||||||
@@ -2,13 +2,11 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import hashlib
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import tempfile
|
|
||||||
import typing as t
|
import typing as t
|
||||||
from collections import namedtuple # pylint: disable=syntax-error
|
from collections import namedtuple # pylint: disable=syntax-error
|
||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass
|
||||||
@@ -45,14 +43,13 @@ from .photokit import (
|
|||||||
from .phototemplate import RenderOptions
|
from .phototemplate import RenderOptions
|
||||||
from .rich_utils import add_rich_markup_tag
|
from .rich_utils import add_rich_markup_tag
|
||||||
from .uti import get_preferred_uti_extension
|
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__ = [
|
__all__ = [
|
||||||
"ExportError",
|
"ExportError",
|
||||||
"ExportOptions",
|
"ExportOptions",
|
||||||
"ExportResults",
|
"ExportResults",
|
||||||
"PhotoExporter",
|
"PhotoExporter",
|
||||||
"hexdigest",
|
|
||||||
"rename_jpeg_files",
|
"rename_jpeg_files",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -266,6 +263,7 @@ class ExportResults:
|
|||||||
exported_album=None,
|
exported_album=None,
|
||||||
skipped_album=None,
|
skipped_album=None,
|
||||||
missing_album=None,
|
missing_album=None,
|
||||||
|
metadata_changed=None,
|
||||||
):
|
):
|
||||||
self.exported = exported or []
|
self.exported = exported or []
|
||||||
self.new = new or []
|
self.new = new or []
|
||||||
@@ -292,6 +290,7 @@ class ExportResults:
|
|||||||
self.exported_album = exported_album or []
|
self.exported_album = exported_album or []
|
||||||
self.skipped_album = skipped_album or []
|
self.skipped_album = skipped_album or []
|
||||||
self.missing_album = missing_album or []
|
self.missing_album = missing_album or []
|
||||||
|
self.metadata_changed = metadata_changed or []
|
||||||
|
|
||||||
def all_files(self):
|
def all_files(self):
|
||||||
"""return all filenames contained in results"""
|
"""return all filenames contained in results"""
|
||||||
@@ -342,6 +341,7 @@ class ExportResults:
|
|||||||
self.exported_album += other.exported_album
|
self.exported_album += other.exported_album
|
||||||
self.skipped_album += other.skipped_album
|
self.skipped_album += other.skipped_album
|
||||||
self.missing_album += other.missing_album
|
self.missing_album += other.missing_album
|
||||||
|
self.metadata_changed += other.metadata_changed
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -371,12 +371,14 @@ class ExportResults:
|
|||||||
+ f",exported_album={self.exported_album}"
|
+ f",exported_album={self.exported_album}"
|
||||||
+ f",skipped_album={self.skipped_album}"
|
+ f",skipped_album={self.skipped_album}"
|
||||||
+ f",missing_album={self.missing_album}"
|
+ f",missing_album={self.missing_album}"
|
||||||
|
+ f",metadata_changed={self.metadata_changed}"
|
||||||
+ ")"
|
+ ")"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PhotoExporter:
|
class PhotoExporter:
|
||||||
"""Export a photo"""
|
"""Export a photo"""
|
||||||
|
|
||||||
def __init__(self, photo: "PhotoInfo", tmpdir: t.Optional[str] = None):
|
def __init__(self, photo: "PhotoInfo", tmpdir: t.Optional[str] = None):
|
||||||
self.photo = photo
|
self.photo = photo
|
||||||
self._render_options = RenderOptions()
|
self._render_options = RenderOptions()
|
||||||
@@ -739,7 +741,7 @@ class PhotoExporter:
|
|||||||
return ShouldUpdate.EDITED_SIG_DIFFERENT
|
return ShouldUpdate.EDITED_SIG_DIFFERENT
|
||||||
|
|
||||||
if options.force_update:
|
if options.force_update:
|
||||||
current_digest = hexdigest(self.photo.json())
|
current_digest = self.photo.hexdigest
|
||||||
if current_digest != file_record.digest:
|
if current_digest != file_record.digest:
|
||||||
# metadata in Photos changed, force update
|
# metadata in Photos changed, force update
|
||||||
return ShouldUpdate.DIGEST_DIFFERENT
|
return ShouldUpdate.DIGEST_DIFFERENT
|
||||||
@@ -1179,8 +1181,9 @@ class PhotoExporter:
|
|||||||
rec.dest_sig = fileutil.file_sig(dest)
|
rec.dest_sig = fileutil.file_sig(dest)
|
||||||
if options.exiftool:
|
if options.exiftool:
|
||||||
rec.exifdata = self._exiftool_json_sidecar(options)
|
rec.exifdata = self._exiftool_json_sidecar(options)
|
||||||
if options.force_update:
|
if self.photo.hexdigest != rec.digest:
|
||||||
rec.digest = hexdigest(photoinfo)
|
results.metadata_changed = [dest_str]
|
||||||
|
rec.digest = self.photo.hexdigest
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@@ -2011,13 +2014,6 @@ class PhotoExporter:
|
|||||||
f.close()
|
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):
|
def _check_export_suffix(src, dest, edited):
|
||||||
"""Helper function for exporting photos to check file extensions of destination path.
|
"""Helper function for exporting photos to check file extensions of destination path.
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import os
|
|||||||
import os.path
|
import os.path
|
||||||
import pathlib
|
import pathlib
|
||||||
from datetime import timedelta, timezone
|
from datetime import timedelta, timezone
|
||||||
|
from functools import cached_property
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
@@ -54,7 +55,7 @@ from .scoreinfo import ScoreInfo
|
|||||||
from .searchinfo import SearchInfo
|
from .searchinfo import SearchInfo
|
||||||
from .text_detection import detect_text
|
from .text_detection import detect_text
|
||||||
from .uti import get_preferred_uti_extension, get_uti_for_extension
|
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"]
|
__all__ = ["PhotoInfo", "PhotoInfoNone"]
|
||||||
|
|
||||||
@@ -1356,6 +1357,12 @@ class PhotoInfo:
|
|||||||
self._exiftool = exiftool
|
self._exiftool = exiftool
|
||||||
return self._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):
|
def detected_text(self, confidence_threshold=TEXT_DETECTION_CONFIDENCE_THRESHOLD):
|
||||||
"""Detects text in photo and returns lists of results as (detected text, confidence)
|
"""Detects text in photo and returns lists of results as (detected text, confidence)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import glob
|
import glob
|
||||||
|
import hashlib
|
||||||
import importlib
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
@@ -29,6 +30,7 @@ __all__ = [
|
|||||||
"expand_and_validate_filepath",
|
"expand_and_validate_filepath",
|
||||||
"get_last_library_path",
|
"get_last_library_path",
|
||||||
"get_system_library_path",
|
"get_system_library_path",
|
||||||
|
"hexdigest",
|
||||||
"increment_filename_with_count",
|
"increment_filename_with_count",
|
||||||
"increment_filename",
|
"increment_filename",
|
||||||
"lineno",
|
"lineno",
|
||||||
@@ -517,3 +519,10 @@ def get_latest_version() -> Tuple[Optional[str], str]:
|
|||||||
def pluralize(count, singular, plural):
|
def pluralize(count, singular, plural):
|
||||||
"""Return singular or plural based on count"""
|
"""Return singular or plural based on count"""
|
||||||
return singular if count == 1 else plural
|
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