Last active
January 25, 2021 02:28
-
-
Save RhetTbull/3db332106631cef1d582b0f9eb8847a6 to your computer and use it in GitHub Desktop.
Proof of concept to merge Apple Photos libraries -- still very unfinished
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Proof of concept showing how to merge Apple Photos libraries including most of the metadata """ | |
# Currently working: | |
# * places photos into correct albums and folder structure | |
# * sets title, description, keywords, location | |
# Limitations: | |
# * doesn't currently handle Live Photos or RAW+JPEG pairs | |
# * only merges the most recent version of the photo (edit history is lost) | |
# * very limited error handling | |
# * doesn't merge Person In Image | |
import pathlib | |
import tempfile | |
import click | |
import photoscript | |
import osxphotos | |
@click.command() | |
@click.argument("src_library", metavar="SOURCE", nargs=1, type=click.Path(exists=True)) | |
@click.argument( | |
"dest_library", metavar="DESTINATION", nargs=1, type=click.Path(exists=True) | |
) | |
@click.pass_context | |
def cli(ctx, src_library, dest_library): | |
click.echo(f"Opening destination library {dest_library}") | |
dest = photoscript.PhotosLibrary() | |
dest.open(dest_library) | |
click.echo(f"Opening source library {src_library}") | |
src = osxphotos.PhotosDB(dbfile=src_library) | |
src_photos = src.photos() | |
click.echo(f"Merging {len(src_photos)} photos from {src_library} to {dest_library}") | |
with click.progressbar(src_photos) as bar: | |
for src_photo in bar: | |
path = src_photo.path_edited if src_photo.hasadjustments else src_photo.path | |
if not path: | |
click.secho( | |
f"Skipping missing photo {src_photo.original_filename} ({src_photo.uuid})", | |
fg="red", | |
) | |
continue | |
# export photo to temp file and rename to original_filename | |
# handling of RAW+JPEG pairs, Live photos, etc. left as exercise for the reader | |
# RAW+JPEG pairs will be correctly handled if imported like this: | |
# dest.import_photos(["/Users/rhet/Desktop/export/IMG_1994.JPG", "/Users/rhet/Desktop/export/IMG_1994.cr2"]) | |
# Live Photos will be correctly handled if imported like this: | |
# dest.import_photos(["/Users/rhet/Desktop/export/IMG_3259.HEIC","/Users/rhet/Desktop/export/IMG_3259.mov"]) | |
with tempfile.TemporaryDirectory() as tmpdir: | |
# get right suffix for original or edited file | |
ext = pathlib.Path(path).suffix | |
dest_file = pathlib.Path(src_photo.original_filename).stem + ext | |
exported = src_photo.export( | |
tmpdir, dest_file, edited=src_photo.hasadjustments | |
) | |
if exported: | |
exported = [ | |
str(pathlib.Path(tmpdir) / filename) for filename in exported | |
] | |
dest_photos = dest.import_photos( | |
exported, skip_duplicate_check=True | |
) | |
else: | |
click.secho( | |
f"Error exporting photo {src_photo.original_filename} ({src_photo.uuid})", | |
fg="red", | |
) | |
continue | |
if not dest_photos: | |
click.secho( | |
f"Error importing photo {src_photo.original_filename} ({src_photo.uuid})", | |
fg="red", | |
) | |
continue | |
for dest_photo in dest_photos: | |
dest_photo.description = src_photo.description | |
dest_photo.title = src_photo.title | |
if src_photo.persons: | |
# add keywords for each person | |
dest_photo.keywords = src_photo.keywords + [ | |
f"People/{p}" for p in src_photo.persons | |
] | |
else: | |
dest_photo.keywords = src_photo.keywords | |
if src_photo.location[0]: | |
dest_photo.location = src_photo.location | |
for album in src_photo.album_info: | |
if album.folder_names: | |
# make_folders silently ignores existing folders (like os.makedirs) | |
folder = dest.make_folders(album.folder_names) | |
dest_album = folder.album(album.title) | |
if not dest_album: | |
# album doesn't exist | |
dest_album = folder.create_album(album.title) | |
else: | |
dest_album = dest.album(album.title) | |
if not dest_album: | |
dest_album = dest.create_album(album.title) | |
dest_album.add([dest_photo]) | |
if __name__ == "__main__": | |
cli() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment