Add --add-exported-to-album, # 428

This commit is contained in:
Rhet Turnbull
2021-05-01 21:15:31 -07:00
parent 64379f313e
commit cd8dd552a4
26 changed files with 556 additions and 26 deletions

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.42.13"
__version__ = "0.42.14"

View File

@@ -14,6 +14,7 @@ import unicodedata
import bitmath
import click
import osxmetadata
import photoscript
import yaml
import osxphotos
@@ -53,6 +54,7 @@ from .photoinfo import ExportResults
from .photokit import check_photokit_authorization, request_photokit_authorization
from .queryoptions import QueryOptions
from .utils import get_preferred_uti_extension
from .photosalbum import PhotosAlbum
# global variable to control verbose output
# set via --verbose/-V
@@ -878,6 +880,30 @@ def cli(ctx, db, json_, debug):
"for example, your own scripts or other files. Be sure this is what you intend before using "
"--cleanup. Use --dry-run with --cleanup first if you're not certain.",
)
@click.option(
"--add-exported-to-album",
metavar="ALBUM",
help="Add all exported photos to album ALBUM in Photos. Album ALBUM will be created "
"if it doesn't exist. All exported photos will be added to this album. "
"This only works if the Photos library being exported is the last-opened (default) library in Photos. "
"This feature is currently experimental. I don't know how well it will work on large export sets.",
)
@click.option(
"--add-skipped-to-album",
metavar="ALBUM",
help="Add all skipped photos to album ALBUM in Photos. Album ALBUM will be created "
"if it doesn't exist. All skipped photos will be added to this album. "
"This only works if the Photos library being exported is the last-opened (default) library in Photos. "
"This feature is currently experimental. I don't know how well it will work on large export sets.",
)
@click.option(
"--add-missing-to-album",
metavar="ALBUM",
help="Add all missing photos to album ALBUM in Photos. Album ALBUM will be created "
"if it doesn't exist. All missing photos will be added to this album. "
"This only works if the Photos library being exported is the last-opened (default) library in Photos. "
"This feature is currently experimental. I don't know how well it will work on large export sets.",
)
@click.option(
"--exportdb",
metavar="EXPORTDB_FILE",
@@ -1027,6 +1053,9 @@ def export(
use_photokit,
report,
cleanup,
add_exported_to_album,
add_skipped_to_album,
add_missing_to_album,
exportdb,
load_config,
save_config,
@@ -1180,6 +1209,9 @@ def export(
use_photokit = cfg.use_photokit
report = cfg.report
cleanup = cfg.cleanup
add_exported_to_album = cfg.add_exported_to_album
add_skipped_to_album = cfg.add_skipped_to_album
add_missing_to_album = cfg.add_missing_to_album
exportdb = cfg.exportdb
beta = cfg.beta
only_new = cfg.only_new
@@ -1524,6 +1556,24 @@ def export(
original_name = not current_name
results = ExportResults()
# set up for --add-export-to-album if needed
album_export = (
PhotosAlbum(add_exported_to_album, verbose=verbose_)
if add_exported_to_album
else None
)
album_skipped = (
PhotosAlbum(add_skipped_to_album, verbose=verbose_)
if add_skipped_to_album
else None
)
album_missing = (
PhotosAlbum(add_missing_to_album, verbose=verbose_)
if add_missing_to_album
else None
)
# send progress bar output to /dev/null if verbose to hide the progress bar
fp = open(os.devnull, "w") if verbose else None
with click.progressbar(photos, file=fp) as bar:
@@ -1571,6 +1621,46 @@ def export(
replace_keywords=replace_keywords,
retry=retry,
)
if album_export and export_results.exported:
try:
album_export.add(p)
export_results.exported_album = [
(filename, album_export.name)
for filename in export_results.exported
]
except Exception as e:
click.echo(
f"Error adding photo {p.original_filename} ({p.uuid}) to album {album_export.name}: {e}",
err=True,
)
if album_skipped and export_results.skipped:
try:
album_skipped.add(p)
export_results.skipped_album = [
(filename, album_skipped.name)
for filename in export_results.skipped
]
except Exception as e:
click.echo(
f"Error adding photo {p.original_filename} ({p.uuid}) to album {album_skipped.name}: {e}",
err=True,
)
if album_missing and export_results.missing:
try:
album_missing.add(p)
export_results.missing_album = [
(filename, album_missing.name)
for filename in export_results.missing
]
except Exception as e:
click.echo(
f"Error adding photo {p.original_filename} ({p.uuid}) to album {album_missing.name}: {e}",
err=True,
)
results += export_results
# all photo files (not including sidecars) that are part of this export set
@@ -2784,7 +2874,6 @@ def write_export_report(report_file, results):
"""
# Collect results for reporting
# TODO: pull this in a separate write_report function
all_results = {
result: {
"filename": result,
@@ -2806,6 +2895,7 @@ def write_export_report(report_file, results):
"extended_attributes_skipped": 0,
"cleanup_deleted_file": 0,
"cleanup_deleted_directory": 0,
"exported_album": "",
}
for result in results.all_files()
+ results.deleted_files
@@ -2881,6 +2971,9 @@ def write_export_report(report_file, results):
for result in results.deleted_directories:
all_results[result]["cleanup_deleted_directory"] = 1
for result, album in results.exported_album:
all_results[result]["exported_album"] = album
report_columns = [
"filename",
"exported",
@@ -2901,6 +2994,7 @@ def write_export_report(report_file, results):
"extended_attributes_skipped",
"cleanup_deleted_file",
"cleanup_deleted_directory",
"exported_album",
]
try:

View File

@@ -89,6 +89,9 @@ class ExportResults:
xattr_skipped=None,
deleted_files=None,
deleted_directories=None,
exported_album=None,
skipped_album=None,
missing_album=None,
):
self.exported = exported or []
self.new = new or []
@@ -111,6 +114,9 @@ class ExportResults:
self.xattr_skipped = xattr_skipped or []
self.deleted_files = deleted_files or []
self.deleted_directories = deleted_directories or []
self.exported_album = exported_album or []
self.skipped_album = skipped_album or []
self.missing_album = missing_album or []
def all_files(self):
""" return all filenames contained in results """
@@ -157,6 +163,10 @@ class ExportResults:
self.exiftool_error += other.exiftool_error
self.deleted_files += other.deleted_files
self.deleted_directories += other.deleted_directories
self.exported_album += other.exported_album
self.skipped_album += other.skipped_album
self.missing_album += other.missing_album
return self
def __str__(self):
@@ -181,6 +191,9 @@ class ExportResults:
+ f",exiftool_error={self.exiftool_error}"
+ f",deleted_files={self.deleted_files}"
+ f",deleted_directories={self.deleted_directories}"
+ f",exported_album={self.exported_album}"
+ f",skipped_album={self.skipped_album}"
+ f",missing_album={self.missing_album}"
+ ")"
)
@@ -621,7 +634,11 @@ def export2(
)
edited_name = pathlib.Path(self.path_edited).name
edited_suffix = pathlib.Path(edited_name).suffix
fname = pathlib.Path(self.original_filename).stem + edited_identifier + edited_suffix
fname = (
pathlib.Path(self.original_filename).stem
+ edited_identifier
+ edited_suffix
)
else:
fname = self.original_filename
@@ -1654,7 +1671,7 @@ def _exiftool_dict(
exif["QuickTime:ModifyDate"] = datetime_tz_to_utc(
self.date_modified
).strftime("%Y:%m:%d %H:%M:%S")
# remove any new lines in any fields
for field, val in exif.items():
if type(val) == str:

74
osxphotos/photosalbum.py Normal file
View File

@@ -0,0 +1,74 @@
""" PhotosAlbum class to create an album in default Photos library and add photos to it """
from typing import Optional
import photoscript
from .photoinfo import PhotoInfo
from .utils import noop
class PhotosAlbum:
def __init__(self, name: str, verbose: Optional[callable] = None):
self.name = name
self.verbose = verbose or noop
self.library = photoscript.PhotosLibrary()
album = self.library.album(name)
if album is None:
self.verbose(f"Creating Photos album '{self.name}'")
album = self.library.create_album(name)
self.album = album
def add(self, photo: PhotoInfo):
photo_ = photoscript.Photo(photo.uuid)
self.album.add([photo_])
self.verbose(
f"Added {photo.original_filename} ({photo.uuid}) to album {self.name}"
)
def photos(self):
return self.album.photos()
# def add_photo_to_album(photo, album_pairs, results):
# # todo: class PhotoAlbum
# # keeps a name, maintains state
# """ add photo to album(s) as defined in album_pairs
# Args:
# photo: PhotoInfo object
# album_pairs: list of tuples with [(album name, results_list)]
# results: ExportResults object
# Returns:
# updated ExportResults object
# """
# for album, result_list in album_pairs:
# try:
# if album_export is None:
# # first time fetching the album, see if it exists already
# album_export = photos_library.album(
# add_exported_to_album
# )
# if album_export is None:
# # album doesn't exist, so create it
# verbose_(
# f"Creating Photos album '{add_exported_to_album}'"
# )
# album_export = photos_library.create_album(
# add_exported_to_album
# )
# exported_photo = photoscript.Photo(p.uuid)
# album_export.add([exported_photo])
# verbose_(
# f"Added {p.original_filename} ({p.uuid}) to album {add_exported_to_album}"
# )
# exported_album = [
# (filename, add_exported_to_album)
# for filename in export_results.exported
# ]
# export_results.exported_album = exported_album
# if
# except Exception as e:
# click.echo(
# f"Error adding photo {p.original_filename} ({p.uuid}) to album {add_exported_to_album}"
# )