Add --add-exported-to-album, # 428
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.42.13"
|
||||
__version__ = "0.42.14"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
74
osxphotos/photosalbum.py
Normal 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}"
|
||||
# )
|
||||
Reference in New Issue
Block a user