Fixed album sort order for custom sort, #497

This commit is contained in:
Rhet Turnbull
2021-07-20 05:41:13 -07:00
parent e752f3c7a7
commit e27c40c772
63 changed files with 259 additions and 68 deletions

View File

@@ -1815,7 +1815,7 @@ Substitution Description
{lf} A line feed: '\n', alias for {newline}
{cr} A carriage return: '\r'
{crlf} a carriage return + line feed: '\r\n'
{osxphotos_version} The osxphotos version, e.g. '0.42.64'
{osxphotos_version} The osxphotos version, e.g. '0.42.65'
{osxphotos_cmd_line} The full command line used to run osxphotos
The following substitutions may result in multiple values. Thus if specified for
@@ -3038,6 +3038,17 @@ Returns a list of [FolderInfo](#FolderInfo) objects representing the sub-folders
#### `parent`
Returns a [FolderInfo](#FolderInfo) object representing the folder's parent folder or `None` if album is not a in a folder.
#### `sort_order`
Returns album sort order (as `AlbumSortOrder` enum). On Photos <=4, always returns `AlbumSortOrder.MANUAL`.
`AlbumSortOrder` has following values:
- `UNKNOWN`
- `MANUAL`
- `NEWEST_FIRST`
- `OLDEST_FIRST`
- `TITLE`
#### `photo_index(photo)`
Returns index of photo in album (based on album sort order).
@@ -3652,7 +3663,7 @@ The following template field substitutions are availabe for use the templating s
|{lf}|A line feed: '\n', alias for {newline}|
|{cr}|A carriage return: '\r'|
|{crlf}|a carriage return + line feed: '\r\n'|
|{osxphotos_version}|The osxphotos version, e.g. '0.42.64'|
|{osxphotos_version}|The osxphotos version, e.g. '0.42.65'|
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|{album}|Album(s) photo is contained in|
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|

View File

@@ -1,3 +1,4 @@
from ._constants import AlbumSortOrder
from ._version import __version__
from .exiftool import ExifTool
from .photoinfo import ExportResults, PhotoInfo

View File

@@ -4,6 +4,7 @@ Constants used by osxphotos
import os.path
from datetime import datetime
from enum import Enum
OSXPHOTOS_URL = "https://github.com/RhetTbull/osxphotos"
@@ -273,3 +274,11 @@ POST_COMMAND_CATEGORIES = {
# "deleted_files": "When used with '--cleanup', all files deleted during the export",
# "deleted_directories": "When used with '--cleanup', all directories deleted during the export",
}
class AlbumSortOrder(Enum):
"""Album Sort Order"""
UNKNOWN = 0
MANUAL = 1
NEWEST_FIRST = 2
OLDEST_FIRST = 3
TITLE = 5

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.42.64"
__version__ = "0.42.65"

View File

@@ -19,6 +19,7 @@ from ._constants import (
_PHOTOS_5_ALBUM_KIND,
_PHOTOS_5_FOLDER_KIND,
TIME_DELTA,
AlbumSortOrder,
)
from .datetime_utils import get_local_tz
@@ -156,7 +157,17 @@ class AlbumInfo(AlbumInfoBaseClass):
if self.uuid in self._db._dbalbums_album:
uuid, sort_order = zip(*self._db._dbalbums_album[self.uuid])
sorted_uuid = sort_list_by_keys(uuid, sort_order)
self._photos = self._db.photos_by_uuid(sorted_uuid)
photos = self._db.photos_by_uuid(sorted_uuid)
sort_order = self.sort_order
if sort_order == AlbumSortOrder.NEWEST_FIRST:
self._photos = sorted(photos, key=lambda p: p.date, reverse=True)
elif sort_order == AlbumSortOrder.OLDEST_FIRST:
self._photos = sorted(photos, key=lambda p: p.date)
elif sort_order == AlbumSortOrder.TITLE:
self._photos = sorted(photos, key=lambda p: p.title or "")
else:
# assume AlbumSortOrder.MANUAL
self._photos = photos
else:
self._photos = []
return self._photos
@@ -209,6 +220,27 @@ class AlbumInfo(AlbumInfoBaseClass):
)
return self._parent
@property
def sort_order(self):
"""return sort order of album"""
if self._db._db_version <= _PHOTOS_4_VERSION:
return AlbumSortOrder.MANUAL
details = self._db._dbalbum_details[self._uuid]
if details["customsortkey"] == 1:
if details["customsortascending"] == 0:
return AlbumSortOrder.NEWEST_FIRST
elif details["customsortascending"] == 1:
return AlbumSortOrder.OLDEST_FIRST
else:
return AlbumSortOrder.UNKNOWN
elif details["customsortkey"] == 5:
return AlbumSortOrder.TITLE
elif details["customsortkey"] == 0:
return AlbumSortOrder.MANUAL
else:
return AlbumSortOrder.UNKNOWN
def photo_index(self, photo):
"""return index of photo in album (based on album sort order)"""
index = 0

View File

@@ -790,6 +790,8 @@ class PhotosDB:
"creation_date": album[8],
"start_date": None, # Photos 5 only
"end_date": None, # Photos 5 only
"customsortascending": None, # Photos 5 only
"customsortkey": None, # Photos 5 only
}
# get details about folders
@@ -1767,7 +1769,9 @@ class PhotosDB:
"ZTRASHEDSTATE, " # 9
"ZCREATIONDATE, " # 10
"ZSTARTDATE, " # 11
"ZENDDATE " # 12
"ZENDDATE, " # 12
"ZCUSTOMSORTASCENDING, " # 13
"ZCUSTOMSORTKEY " # 14
"FROM ZGENERICALBUM "
)
for album in c:
@@ -1787,6 +1791,8 @@ class PhotosDB:
"creation_date": album[10],
"start_date": album[11],
"end_date": album[12],
"customsortascending": album[13],
"customsortkey": album[14],
}
# add cross-reference by pk to uuid

View File

@@ -7,7 +7,7 @@
<key>hostuuid</key>
<string>585B80BF-8D1F-55EF-A9E8-6CF4E5523959</string>
<key>pid</key>
<integer>570</integer>
<integer>1961</integer>
<key>processname</key>
<string>photolibraryd</string>
<key>uid</key>

View File

@@ -3,24 +3,24 @@
<plist version="1.0">
<dict>
<key>BackgroundHighlightCollection</key>
<date>2021-03-13T16:38:25Z</date>
<date>2021-07-20T05:48:01Z</date>
<key>BackgroundHighlightEnrichment</key>
<date>2021-03-13T16:38:24Z</date>
<date>2021-07-20T05:48:00Z</date>
<key>BackgroundJobAssetRevGeocode</key>
<date>2021-03-13T16:38:25Z</date>
<date>2021-07-20T07:05:31Z</date>
<key>BackgroundJobSearch</key>
<date>2021-03-13T16:38:25Z</date>
<date>2021-07-20T05:48:01Z</date>
<key>BackgroundPeopleSuggestion</key>
<date>2021-03-13T16:38:23Z</date>
<date>2021-07-20T05:48:00Z</date>
<key>BackgroundUserBehaviorProcessor</key>
<date>2021-03-13T16:38:25Z</date>
<date>2021-07-20T05:48:01Z</date>
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
<date>2020-10-17T23:45:33Z</date>
<date>2021-07-20T05:48:08Z</date>
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
<date>2020-10-17T23:45:24Z</date>
<date>2021-07-20T05:47:59Z</date>
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
<date>2021-03-13T16:38:25Z</date>
<date>2021-07-20T05:48:01Z</date>
<key>SiriPortraitDonation</key>
<date>2021-03-13T16:38:25Z</date>
<date>2021-07-20T05:48:01Z</date>
</dict>
</plist>

View File

@@ -3,8 +3,8 @@
<plist version="1.0">
<dict>
<key>FaceIDModelLastGenerationKey</key>
<date>2020-10-17T23:45:32Z</date>
<date>2021-07-20T05:48:02Z</date>
<key>LastContactClassificationKey</key>
<date>2020-10-17T23:45:54Z</date>
<date>2021-07-20T05:48:05Z</date>
</dict>
</plist>

View File

@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>coalesceDate</key>
<date>2020-04-11T19:26:12Z</date>
<date>2021-07-20T12:21:20Z</date>
<key>coalescePayloadVersion</key>
<integer>1</integer>
<key>currentPayloadVersion</key>

View File

@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>coalesceDate</key>
<date>2020-05-29T15:20:05Z</date>
<date>2021-07-20T12:21:21Z</date>
<key>coalescePayloadVersion</key>
<integer>10</integer>
<key>currentPayloadVersion</key>

View File

@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>coalesceDate</key>
<date>2019-10-27T15:36:05Z</date>
<date>2021-07-20T12:21:20Z</date>
<key>coalescePayloadVersion</key>
<integer>1</integer>
<key>currentPayloadVersion</key>

View File

@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>coalesceDate</key>
<date>2020-05-29T15:20:05Z</date>
<date>2021-07-20T12:21:21Z</date>
<key>coalescePayloadVersion</key>
<integer>1</integer>
<key>currentPayloadVersion</key>

View File

@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>coalesceDate</key>
<date>2020-05-29T15:20:05Z</date>
<date>2021-07-20T12:21:20Z</date>
<key>coalescePayloadVersion</key>
<integer>1</integer>
<key>currentPayloadVersion</key>

View File

@@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>coalesceDate</key>
<date>2021-07-20T12:21:20Z</date>
<key>coalescePayloadVersion</key>
<integer>1</integer>
<key>currentPayloadVersion</key>
<integer>1</integer>
<key>snapshotDate</key>

View File

@@ -2,11 +2,11 @@ import pytest
import osxphotos
from osxphotos._constants import _UNKNOWN_PERSON
from osxphotos._constants import _UNKNOWN_PERSON, AlbumSortOrder
PHOTOS_DB = "./tests/Test-10.15.4.photoslibrary/database/photos.db"
PHOTOS_DB = "./tests/Test-10.15.7.photoslibrary/database/photos.db"
TOP_LEVEL_FOLDERS = ["Folder1", "Folder2"]
TOP_LEVEL_FOLDERS = ["Folder1", "Folder2", "Pumpkin Farm"]
TOP_LEVEL_CHILDREN = ["SubFolder1", "SubFolder2"]
@@ -15,25 +15,73 @@ FOLDER_ALBUM_DICT = {
"SubFolder1": [],
"SubFolder2": ["AlbumInFolder"],
"Folder2": ["Raw"],
"Pumpkin Farm": [],
}
ALBUM_NAMES = ["Pumpkin Farm", "AlbumInFolder", "Test Album", "Test Album", "Raw"]
ALBUM_NAMES = [
"2018-10 - Sponsion, Museum, Frühstück, Römermuseum",
"2019-10/11 Paris Clermont",
"AlbumInFolder",
"EmptyAlbum",
"I have a deleted twin",
"Multi Keyword",
"Pumpkin Farm",
"Raw",
"Sorted Manual",
"Sorted Newest First",
"Sorted Oldest First",
"Sorted Title",
"Test Album",
"Test Album",
]
ALBUM_PARENT_DICT = {
"Pumpkin Farm": None,
"2018-10 - Sponsion, Museum, Frühstück, Römermuseum": None,
"2019-10/11 Paris Clermont": None,
"AlbumInFolder": "SubFolder2",
"Test Album": None,
"EmptyAlbum": None,
"I have a deleted twin": None,
"Multi Keyword": None,
"Pumpkin Farm": None,
"Raw": "Folder2",
"Sorted Manual": None,
"Sorted Newest First": None,
"Sorted Oldest First": None,
"Sorted Title": None,
"Test Album": None,
}
ALBUM_FOLDER_NAMES_DICT = {
"Pumpkin Farm": [],
"2018-10 - Sponsion, Museum, Frühstück, Römermuseum": [],
"2019-10/11 Paris Clermont": [],
"AlbumInFolder": ["Folder1", "SubFolder2"],
"Test Album": [],
"EmptyAlbum": [],
"I have a deleted twin": [],
"Multi Keyword": [],
"Pumpkin Farm": [],
"Raw": ["Folder2"],
"Sorted Manual": [],
"Sorted Newest First": [],
"Sorted Oldest First": [],
"Sorted Title": [],
"Test Album": [],
}
ALBUM_LEN_DICT = {"Pumpkin Farm": 3, "AlbumInFolder": 2, "Test Album": 1, "Raw": 4}
ALBUM_LEN_DICT = {
"2018-10 - Sponsion, Museum, Frühstück, Römermuseum": 1,
"2019-10/11 Paris Clermont": 1,
"AlbumInFolder": 2,
"EmptyAlbum": 0,
"I have a deleted twin": 1,
"Multi Keyword": 2,
"Pumpkin Farm": 3,
"Raw": 4,
"Sorted Manual": 3,
"Sorted Newest First": 3,
"Sorted Oldest First": 3,
"Sorted Title": 3,
"Test Album": 1,
}
ALBUM_PHOTO_UUID_DICT = {
"Pumpkin Farm": [
@@ -58,10 +106,33 @@ ALBUM_PHOTO_UUID_DICT = {
}
UUID_DICT = {
"two_albums": "F12384F6-CD17-4151-ACBA-AE0E3688539E",
"six_albums": "F12384F6-CD17-4151-ACBA-AE0E3688539E",
"album_dates": "0C514A98-7B77-4E4F-801B-364B7B65EAFA",
}
UUID_DICT_SORT_ORDER = {
AlbumSortOrder.MANUAL: [
"7783E8E6-9CAC-40F3-BE22-81FB7051C266",
"3DD2C897-F19E-4CA6-8C22-B027D5A71907",
"F12384F6-CD17-4151-ACBA-AE0E3688539E",
],
AlbumSortOrder.NEWEST_FIRST: [
"7783E8E6-9CAC-40F3-BE22-81FB7051C266",
"F12384F6-CD17-4151-ACBA-AE0E3688539E",
"3DD2C897-F19E-4CA6-8C22-B027D5A71907",
],
AlbumSortOrder.OLDEST_FIRST: [
"3DD2C897-F19E-4CA6-8C22-B027D5A71907",
"F12384F6-CD17-4151-ACBA-AE0E3688539E",
"7783E8E6-9CAC-40F3-BE22-81FB7051C266",
],
AlbumSortOrder.TITLE: [
"7783E8E6-9CAC-40F3-BE22-81FB7051C266",
"F12384F6-CD17-4151-ACBA-AE0E3688539E",
"3DD2C897-F19E-4CA6-8C22-B027D5A71907",
],
}
@pytest.fixture(scope="module")
def photosdb():
@@ -186,6 +257,7 @@ def test_albums_photos(photosdb):
photos = album.photos
assert len(photos) == ALBUM_LEN_DICT[album.title]
assert len(photos) == len(album)
if album.title in ALBUM_PHOTO_UUID_DICT:
for photo in photos:
assert photo.uuid in ALBUM_PHOTO_UUID_DICT[album.title]
@@ -237,19 +309,67 @@ def test_photoinfo_albums(photosdb):
def test_photoinfo_albums_2(photosdb):
"""Test that PhotoInfo.albums returns only number albums expected"""
photos = photosdb.photos(uuid=[UUID_DICT["two_albums"]])
photos = photosdb.photos(uuid=[UUID_DICT["six_albums"]])
albums = photos[0].albums
assert len(albums) == 2
assert len(albums) == 6
def test_photoinfo_album_info(photosdb):
"""test PhotoInfo.album_info"""
photos = photosdb.photos(uuid=[UUID_DICT["two_albums"]])
photos = photosdb.photos(uuid=[UUID_DICT["six_albums"]])
album_info = photos[0].album_info
assert len(album_info) == 2
assert album_info[0].title in ["Pumpkin Farm", "Test Album"]
assert album_info[1].title in ["Pumpkin Farm", "Test Album"]
assert len(album_info) == 6
assert album_info[0].title in [
"Pumpkin Farm",
"Test Album",
"Sorted Manual",
"Sorted Newest First",
"Sorted Oldest First",
"Sorted Title",
]
assert album_info[1].title in [
"Pumpkin Farm",
"Test Album",
"Sorted Manual",
"Sorted Newest First",
"Sorted Oldest First",
"Sorted Title",
]
assert photos[0].uuid in [photo.uuid for photo in album_info[0].photos]
def test_album_sort_order(photosdb):
"""Test that AlbumInfo.sort_order is set correctly"""
albums = photosdb.album_info
for album in albums:
if album.title == "Sorted Manual":
assert album.sort_order == AlbumSortOrder.MANUAL
elif album.title == "Sorted Newest First":
assert album.sort_order == AlbumSortOrder.NEWEST_FIRST
elif album.title == "Sorted Oldest First":
assert album.sort_order == AlbumSortOrder.OLDEST_FIRST
elif album.title == "Sorted Title":
assert album.sort_order == AlbumSortOrder.TITLE
def test_album_sort_order_photos(photosdb):
"""Test AlbumInfo.photos returns photos sorted according to AlbumInfo.sort_order"""
albums = photosdb.album_info
for album in albums:
uuids = [photo.uuid for photo in album.photos]
if album.title == "Sorted Manual":
assert album.sort_order == AlbumSortOrder.MANUAL
assert uuids == UUID_DICT_SORT_ORDER[AlbumSortOrder.MANUAL]
if album.title == "Sorted Newest First":
assert album.sort_order == AlbumSortOrder.NEWEST_FIRST
assert uuids == UUID_DICT_SORT_ORDER[AlbumSortOrder.NEWEST_FIRST]
if album.title == "Sorted Oldest First":
assert album.sort_order == AlbumSortOrder.OLDEST_FIRST
assert uuids == UUID_DICT_SORT_ORDER[AlbumSortOrder.OLDEST_FIRST]
if album.title == "Sorted Title":
assert album.sort_order == AlbumSortOrder.TITLE
assert uuids == UUID_DICT_SORT_ORDER[AlbumSortOrder.TITLE]

View File

@@ -49,45 +49,53 @@ KEYWORDS = [
# Photos 5 includes blank person for detected face
PERSONS = ["Katie", "Suzy", "Maria", _UNKNOWN_PERSON]
ALBUMS = [
"Pumpkin Farm",
"Test Album", # there are 2 albums named "Test Album" for testing duplicate album names
"AlbumInFolder",
"Raw",
"I have a deleted twin", # there's an empty album with same name that has been deleted
"EmptyAlbum",
"2018-10 - Sponsion, Museum, Frühstück, Römermuseum",
"2019-10/11 Paris Clermont",
"AlbumInFolder",
"EmptyAlbum",
"I have a deleted twin", # there's an empty album with same name that has been deleted
"Multi Keyword",
"Pumpkin Farm",
"Raw",
"Sorted Manual",
"Sorted Newest First",
"Sorted Oldest First",
"Sorted Title",
"Test Album", # there are 2 albums named "Test Album" for testing duplicate album names
]
KEYWORDS_DICT = {
"Kids": 4,
"wedding": 3,
"flowers": 1,
"Drink": 2,
"England": 1,
"London": 1,
"Kids": 4,
"London 2018": 1,
"London": 1,
"Maria": 1,
"St. James's Park": 1,
"Travel": 2,
"UK": 1,
"United Kingdom": 1,
"foo/bar": 1,
"Travel": 2,
"Maria": 1,
"Drink": 2,
"Val d'Isère": 2,
"Wine": 2,
"Wine Bottle": 2,
"Wine": 2,
"flowers": 1,
"foo/bar": 1,
"wedding": 3,
}
PERSONS_DICT = {"Katie": 3, "Suzy": 2, "Maria": 2, _UNKNOWN_PERSON: 1}
ALBUM_DICT = {
"Pumpkin Farm": 3,
"Test Album": 2,
"AlbumInFolder": 2,
"Raw": 4,
"I have a deleted twin": 1,
"EmptyAlbum": 0,
"2018-10 - Sponsion, Museum, Frühstück, Römermuseum": 1,
"2019-10/11 Paris Clermont": 1,
"AlbumInFolder": 2,
"EmptyAlbum": 0,
"I have a deleted twin": 1,
"Multi Keyword": 2,
"Pumpkin Farm": 3,
"Raw": 4,
"Sorted Manual": 3,
"Sorted Newest First": 3,
"Sorted Oldest First": 3,
"Sorted Title": 3,
"Test Album": 2,
} # Note: there are 2 albums named "Test Album" for testing duplicate album names
UUID_DICT = {

View File

@@ -754,11 +754,11 @@ UUID_IN_ALBUM = [
"4D521201-92AC-43E5-8F7C-59BC41C37A96",
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068",
"3DD2C897-F19E-4CA6-8C22-B027D5A71907",
"7783E8E6-9CAC-40F3-BE22-81FB7051C266",
]
UUID_NOT_IN_ALBUM = [
"A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C",
"7783E8E6-9CAC-40F3-BE22-81FB7051C266",
"DC99FBDD-7A52-4100-A5BB-344131646C30",
"D1359D09-1373-4F3B-B0E3-1A4DE573E4A3",
"E2078879-A29C-4D6F-BACB-E3BBE6C3EB91",
@@ -2649,7 +2649,7 @@ def test_query_label_4():
)
assert result.exit_code == 0
json_got = json.loads(result.output)
assert len(json_got) == 2
assert len(json_got) == 1
def test_query_deleted_deleted_only():
@@ -2883,11 +2883,11 @@ def test_export_sidecar_templates():
exifdata = json.load(jsonfile)
assert (
exifdata[0]["XMP:Description"]
== "Girls with pumpkins Katie, Suzy Kids Pumpkin Farm, Test Album"
== "Girls with pumpkins Katie, Suzy Kids Pumpkin Farm, Sorted Manual, Sorted Newest First, Sorted Oldest First, Sorted Title, Test Album"
)
assert (
exifdata[0]["EXIF:ImageDescription"]
== "Girls with pumpkins Katie, Suzy Kids Pumpkin Farm, Test Album"
== "Girls with pumpkins Katie, Suzy Kids Pumpkin Farm, Sorted Manual, Sorted Newest First, Sorted Oldest First, Sorted Title, Test Album"
)
@@ -2926,11 +2926,11 @@ def test_export_sidecar_templates_exiftool():
exifdata = json.load(jsonfile)
assert (
exifdata[0]["Description"]
== "Girls with pumpkins Katie, Suzy Kids Pumpkin Farm, Test Album"
== "Girls with pumpkins Katie, Suzy Kids Pumpkin Farm, Sorted Manual, Sorted Newest First, Sorted Oldest First, Sorted Title, Test Album"
)
assert (
exifdata[0]["ImageDescription"]
== "Girls with pumpkins Katie, Suzy Kids Pumpkin Farm, Test Album"
== "Girls with pumpkins Katie, Suzy Kids Pumpkin Farm, Sorted Manual, Sorted Newest First, Sorted Oldest First, Sorted Title, Test Album"
)