Source code for osxphotos.photosalbum

""" PhotosAlbum class to create an album in default Photos library and add photos to it """

from typing import List, Optional

import photoscript
from more_itertools import chunked
from photoscript import Album, Folder, Photo, PhotosLibrary

from .photoinfo import PhotoInfo
from .utils import noop, pluralize

__all__ = ["PhotosAlbum", "PhotosAlbumPhotoScript"]


def folder_by_path(folders: List[str], verbose: Optional[callable] = None) -> Folder:
    """Get (and create if necessary) a Photos Folder by path (passed as list of folder names)"""
    library = PhotosLibrary()
    verbose = verbose or noop
    top_folder_name = folders.pop(0)
    top_folder = library.folder(top_folder_name, top_level=True)
    if not top_folder:
        verbose(f"Creating folder '{top_folder_name}'")
        top_folder = library.create_folder(top_folder_name)
    current_folder = top_folder
    for folder_name in folders:
        folder = current_folder.folder(folder_name)
        if not folder:
            verbose(f"Creating folder '{folder_name}'")
            folder = current_folder.create_folder(folder_name)
        current_folder = folder
    return current_folder


def album_by_path(
    folders_album: List[str], verbose: Optional[callable] = None
) -> Album:
    """Get (and create if necessary) a Photos Album by path (pass as list of folders, album name)"""
    library = PhotosLibrary()
    verbose = verbose or noop
    if len(folders_album) > 1:
        # have folders
        album_name = folders_album.pop()
        folder = folder_by_path(folders_album, verbose)
        album = folder.album(album_name)
        if album is None:
            verbose(f"Creating album '{album_name}'")
            album = folder.create_album(album_name)
    else:
        # only have album name
        album_name = folders_album[0]
        album = library.album(album_name, top_level=True)
        if album is None:
            verbose(f"Creating album '{album_name}'")
            album = library.create_album(album_name)

    return album


[docs]class PhotosAlbum: """Add osxphotos.photoinfo.PhotoInfo objects to album""" def __init__( self, name: str, verbose: Optional[callable] = None, split_folder: Optional[str] = None, rich: bool = False, ): """Return a PhotosAlbum object, creating the album if necessary Args: name: Name of album verbose: optional callable to print verbose output split_folder: if set, split album name on value of split_folder to create folders if necessary, e.g. if name = 'folder1/folder2/album' and split_folder='/', then folders 'folder1' and 'folder2' will be created and album 'album' will be created in 'folder2'; if not set, album 'folder1/folder2/album' will be created rich: if True, use rich themes for verbose output """ self.verbose = verbose or noop self.library = photoscript.PhotosLibrary() folders_album = name.split(split_folder) if split_folder else [name] self.album = album_by_path(folders_album, verbose=verbose) self.name = name self.rich = rich def add(self, photo: PhotoInfo): photo_ = photoscript.Photo(photo.uuid) self.album.add([photo_]) self.verbose( f"Added {self._format_name(photo.original_filename)} ({self._format_uuid(photo.uuid)}) to album {self._format_album(self.name)}" ) def add_list(self, photo_list: List[PhotoInfo]): photos = [] for p in photo_list: try: photos.append(photoscript.Photo(p.uuid)) except Exception as e: self.verbose( f"Error creating Photo object for photo {self._format_uuid(p.uuid)}: {e}" ) for photolist in chunked(photos, 10): self.album.add(photolist) photo_len = len(photo_list) self.verbose( f"Added {self._format_num(photo_len)} {pluralize(photo_len, 'photo', 'photos')} to album {self._format_album(self.name)}" ) def photos(self): return self.album.photos() def _format_uuid(self, uuid: str) -> str: """ "Format uuid for verbose output""" return f"[uuid]{uuid}[/uuid]" if self.rich else uuid def _format_album(self, album: str) -> str: """ "Format album name for verbose output""" return f"[filepath]{album}[/filepath]" if self.rich else album def _format_name(self, name: str) -> str: """ "Format name for verbose output""" return f"[filename]{name}[/filename]" if self.rich else name def _format_num(self, num: int) -> str: """ "Format number for verbose output""" return f"[num]{num}[/num]" if self.rich else str(num)
[docs]class PhotosAlbumPhotoScript(PhotosAlbum): """Add photoscript.Photo objects to album""" def add(self, photo: Photo): self.album.add([photo]) self.verbose( f"Added {self._format_name(photo.filename)} ({self._format_uuid(photo.uuid)}) to album {self._format_album(self.name)}" ) def add_list(self, photo_list: List[Photo]): for photolist in chunked(photo_list, 10): self.album.add(photolist) photo_len = len(photo_list) self.verbose( f"Added {self._format_num(photo_len)} {pluralize(photo_len, 'photo', 'photos')} to album {self._format_album(self.name)}" )