-
Notifications
You must be signed in to change notification settings - Fork 99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add option to keep the sort order of an album #496
Comments
You haven't missed anything -- this isn't currently possible with osxphotos. osxphotos does know about the album sort order (see #184) and this information is accessible through the osxphotos python package but due to the way the export code is currently implemented it would be difficult to implement this. The main challenges are:
See also #154 I'll look at the code and think about possible ways to address these issues but no promises on implementing a fix as I do think this might require bigger code changes than I have time to do. |
How about just writing the sort order index number into the sidecar file. Then I can create the correct order myself with an extra script. That would be enough for me. Is this easier to implement? |
@gerrieg I'll look at that but not sure it would be easier, also not sure which field in the sidecar I'd use to do this. The following is a bit hacky (I just threw it together) but it uses osxphotos
Save the following as
The post-function will create a text file named like """ Example function for use with osxphotos export --post-function option showing how to record album sort order """
import pathlib
from typing import Optional
from osxphotos import ExportResults, PhotoInfo
from osxphotos.albuminfo import AlbumInfo
def _get_album_sort_order(album: AlbumInfo, photo: PhotoInfo) -> Optional[int]:
"""Get the sort order of photo in album
Returns: sort order as int or None if photo not found in album
"""
# get the album sort order from the album_info
sort_order = 0 # change this to 1 if you want counting to start at 1
for album_photo in album.photos:
if album_photo.uuid == photo.uuid:
# found the photo we're processing
break
sort_order += 1
else:
# didn't find the photo, so skip this file
return None
return sort_order
def album_sort_order(
photo: PhotoInfo, results: ExportResults, verbose: callable, **kwargs
):
"""Call this with osxphotos export /path/to/export --post-function post_function.py::post_function
This will get called immediately after the photo has been exported
Args:
photo: PhotoInfo instance for the photo that's just been exported
results: ExportResults instance with information about the files associated with the exported photo
verbose: A function to print verbose output if --verbose is set; if --verbose is not set, acts as a no-op (nothing gets printed)
**kwargs: reserved for future use; recommend you include **kwargs so your function still works if additional arguments are added in future versions
Notes:
Use verbose(str) instead of print if you want your function to conditionally output text depending on --verbose flag
Any string printed with verbose that contains "warning" or "error" (case-insensitive) will be printed with the appropriate warning or error color
Will not be called if --dry-run flag is enabled
Will be called immediately after export and before any --post-command commands are executed
"""
# ExportResults has the following properties
# fields with filenames contain the full path to the file
# exported: list of all files exported
# new: list of all new files exported (--update)
# updated: list of all files updated (--update)
# skipped: list of all files skipped (--update)
# exif_updated: list of all files that were updated with --exiftool
# touched: list of all files that had date updated with --touch-file
# converted_to_jpeg: list of files converted to jpeg with --convert-to-jpeg
# sidecar_json_written: list of all JSON sidecar files written
# sidecar_json_skipped: list of all JSON sidecar files skipped (--update)
# sidecar_exiftool_written: list of all exiftool sidecar files written
# sidecar_exiftool_skipped: list of all exiftool sidecar files skipped (--update)
# sidecar_xmp_written: list of all XMP sidecar files written
# sidecar_xmp_skipped: list of all XMP sidecar files skipped (--update)
# missing: list of all missing files
# error: list tuples of (filename, error) for any errors generated during export
# exiftool_warning: list of tuples of (filename, warning) for any warnings generated by exiftool with --exiftool
# exiftool_error: list of tuples of (filename, error) for any errors generated by exiftool with --exiftool
# xattr_written: list of files that had extended attributes written
# xattr_skipped: list of files that where extended attributes were skipped (--update)
# deleted_files: list of deleted files
# deleted_directories: list of deleted directories
# 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
# missing_album: list of tuples of (filename, album_name) for missing files added to album with --add-missing-to-album
for filepath in results.exported:
# do your processing here
filepath = pathlib.Path(filepath)
album_dir = filepath.parent.name
if album_dir not in photo.albums:
return
# get the first album that matches this name of which the photo is a member
album_info = None
for album in photo.album_info:
if album.title == album_dir:
album_info = album
break
else:
# didn't find the album, so skip this file
return
sort_order = _get_album_sort_order(album_info, photo)
if sort_order is None:
# didn't find the photo, so skip this file
return
verbose(f"Sort order for {filepath} in album {album_dir} is {sort_order}")
with open(str(filepath) + "_sort_order.txt", "w") as f:
f.write(str(sort_order)) |
This This is where the dest_path would need to passed: Line 2844 in e40ecc4
Once implemented you could then do something like this:
|
Thanks for your efforts, I will try it out soon. |
@gerrieg I think I have a better solution implemented now. In v0.42.64, I've changed the code so that template functions (custom user-defined plug-ins that work with the template system) can get access to the export path when used with the
This only works when the directory ends with the The
Of course, this suffers the limitation that if you have two albums of the same name in the same folder, they'll be treated as a single folder upon export (but this is true anyway for osxphotos as the filesystem does not allow items with duplicate names to be created). Let me know if you have any questions -- I believe this will do what you originally asked for in this issue. I'll take a look at adding an Implementation note: the code originally evaluated the filename template then the directory template as I'd assumed filename wouldn't change but the same file could be exported to multiple directories based on value of Note: this example contains both the The example
If you want to start sequence at 1 (or any other number), you can do so by setting the
and your files will be named:
""" Example function for use with osxphotos export --post-function option showing how to record album sort order """
import os
import pathlib
from typing import Optional
from osxphotos import ExportResults, PhotoInfo
from osxphotos.albuminfo import AlbumInfo
from osxphotos.path_utils import sanitize_dirname
from osxphotos.phototemplate import RenderOptions
def _get_album_sort_order(album: AlbumInfo, photo: PhotoInfo) -> Optional[int]:
"""Get the sort order of photo in album
Returns: sort order as int or None if photo not found in album
"""
# get the album sort order from the album_info
sort_order = 0 # change this to 1 if you want counting to start at 1
for album_photo in album.photos:
if album_photo.uuid == photo.uuid:
# found the photo we're processing
break
sort_order += 1
else:
# didn't find the photo, so skip this file
return None
return sort_order
def album_sequence(photo: PhotoInfo, options: RenderOptions, **kwargs) -> str:
"""Call this with {function} template to get album sequence (sort order) when exporting with {folder_album} template
For example, calling this template function like the following prepends sequence#_ to each exported file if the file is in an album:
osxphotos export /path/to/export -V --directory "{folder_album}" --filename "{album?{function:examples/album_sort_order.py::album_sequence}_,}{original_name}"
The sequence will start at 0. To change the sequence to start at a different offset (e.g. 1), set the environment variable OSXPHOTOS_ALBUM_SEQUENCE_START=1 (or whatever offset you want)
"""
dest_path = options.dest_path
if not dest_path:
return ""
album_info = None
for album in photo.album_info:
# following code is how {folder_album} builds the folder path
folder = "/".join(sanitize_dirname(f) for f in album.folder_names)
folder += "/" + sanitize_dirname(album.title)
if dest_path.endswith(folder):
album_info = album
break
else:
# didn't find the album, so skip this file
return ""
start_index = int(os.getenv("OSXPHOTOS_ALBUM_SEQUENCE_START", 0))
return str(album_info.photo_index(photo) + start_index)
def album_sort_order(
photo: PhotoInfo, results: ExportResults, verbose: callable, **kwargs
):
"""Call this with osxphotos export /path/to/export --post-function post_function.py::post_function
This will get called immediately after the photo has been exported
Args:
photo: PhotoInfo instance for the photo that's just been exported
results: ExportResults instance with information about the files associated with the exported photo
verbose: A function to print verbose output if --verbose is set; if --verbose is not set, acts as a no-op (nothing gets printed)
**kwargs: reserved for future use; recommend you include **kwargs so your function still works if additional arguments are added in future versions
Notes:
Use verbose(str) instead of print if you want your function to conditionally output text depending on --verbose flag
Any string printed with verbose that contains "warning" or "error" (case-insensitive) will be printed with the appropriate warning or error color
Will not be called if --dry-run flag is enabled
Will be called immediately after export and before any --post-command commands are executed
"""
# ExportResults has the following properties
# fields with filenames contain the full path to the file
# exported: list of all files exported
# new: list of all new files exported (--update)
# updated: list of all files updated (--update)
# skipped: list of all files skipped (--update)
# exif_updated: list of all files that were updated with --exiftool
# touched: list of all files that had date updated with --touch-file
# converted_to_jpeg: list of files converted to jpeg with --convert-to-jpeg
# sidecar_json_written: list of all JSON sidecar files written
# sidecar_json_skipped: list of all JSON sidecar files skipped (--update)
# sidecar_exiftool_written: list of all exiftool sidecar files written
# sidecar_exiftool_skipped: list of all exiftool sidecar files skipped (--update)
# sidecar_xmp_written: list of all XMP sidecar files written
# sidecar_xmp_skipped: list of all XMP sidecar files skipped (--update)
# missing: list of all missing files
# error: list tuples of (filename, error) for any errors generated during export
# exiftool_warning: list of tuples of (filename, warning) for any warnings generated by exiftool with --exiftool
# exiftool_error: list of tuples of (filename, error) for any errors generated by exiftool with --exiftool
# xattr_written: list of files that had extended attributes written
# xattr_skipped: list of files that where extended attributes were skipped (--update)
# deleted_files: list of deleted files
# deleted_directories: list of deleted directories
# 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
# missing_album: list of tuples of (filename, album_name) for missing files added to album with --add-missing-to-album
for filepath in results.exported:
# do your processing here
filepath = pathlib.Path(filepath)
album_dir = filepath.parent.name
if album_dir not in photo.albums:
return
# get the first album that matches this name of which the photo is a member
album_info = None
for album in photo.album_info:
if album.title == album_dir:
album_info = album
break
else:
# didn't find the album, so skip this file
return
sort_order = _get_album_sort_order(album_info, photo)
if sort_order is None:
# didn't find the photo, so skip this file
return
verbose(f"Sort order for {filepath} in album {album_dir} is {sort_order}")
with open(str(filepath) + "_sort_order.txt", "w") as f:
f.write(str(sort_order)) |
Also, this is only guaranteed to work if the album has custom sort order (e.g. you manually arranged the order). If you use the |
The album sort order is now correct for albums sorted with "View | Sort" as of v0.42.65 |
In version 0.42.67, I've added For example, the following command exports photos in the
|
Many thanks for the very fast implementation! 👍 |
In all my albums I sort the photos manually. I have not found a way to export the photos and preserve the sort sequence. Maybe you can add a sequence number in the filename template, sidecar or in the photo metadata?
Or did I miss something?
The text was updated successfully, but these errors were encountered: