Compare commits

...

3 Commits

Author SHA1 Message Date
Rhet Turnbull
a941f66d62 Fixed albums for burst images, closes #401, #403, #404 2021-03-27 08:11:33 -07:00
dependabot[bot]
d77eba12b2 Bump pyyaml from 5.1.2 to 5.4 (#402)
Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.1.2 to 5.4.
- [Release notes](https://github.com/yaml/pyyaml/releases)
- [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES)
- [Commits](https://github.com/yaml/pyyaml/compare/5.1.2...5.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-25 21:06:24 -07:00
Rhet Turnbull
de94fd76de Updated CHANGELOG.md, [skip ci] 2021-03-21 23:18:41 -07:00
10 changed files with 177 additions and 15 deletions

View File

@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v0.41.4](https://github.com/RhetTbull/osxphotos/compare/v0.41.3...v0.41.4)
> 22 March 2021
- Bump pillow from 7.2.0 to 8.1.1 [`#399`](https://github.com/RhetTbull/osxphotos/pull/399)
- Added --from-time, --to-time, closes #400 [`#400`](https://github.com/RhetTbull/osxphotos/issues/400)
#### [v0.41.3](https://github.com/RhetTbull/osxphotos/compare/v0.41.2...v0.41.3)
> 14 March 2021

View File

@@ -1394,7 +1394,7 @@ Returns a list of the keywords found in the Photos library
albums = photosdb.album_info
```
Returns a list of [AlbumInfo](#AlbumInfo) objects representing albums in the database or empty list if there are no albums. See also [albums](#albums).
Returns a list of [AlbumInfo](#AlbumInfo) objects representing albums in the database or empty list if there are no albums. See also [albums](#albums) and [burst_album_info](#burst_album_info).
#### `albums`
```python
@@ -1402,7 +1402,7 @@ Returns a list of [AlbumInfo](#AlbumInfo) objects representing albums in the dat
album_names = photosdb.albums
```
Returns a list of the album names found in the Photos library.
Returns a list of the album names found in the Photos library. See also [burst_albums](#burst_albums).
**Note**: In Photos 5.0 (MacOS 10.15/Catalina), It is possible to have more than one album with the same name in Photos. Albums with duplicate names are treated as a single album and the photos in each are combined. For example, if you have two albums named "Wedding" and each has 2 photos, osxphotos will treat this as a single album named "Wedding" with 4 photos in it.
@@ -1838,6 +1838,9 @@ Returns Uniform Type Identifier (UTI) for the associated raw image, if there is
Returns True if photos is a burst image (e.g. part of a set of burst images), otherwise False.
See [burst_photos](#burst_photos)
#### `burst_selected`
Returns True if photo is a burst photo and has been selected from the burst set by the user, otherwise False.
#### `burst_photos`
If photo is a burst image (see [burst](#burst)), returns a list of PhotoInfo objects for all other photos in the same burst set. If not a burst image, returns empty list.
@@ -1861,6 +1864,12 @@ IMG_9854.JPG
IMG_9855.JPG
```
#### `burst_albums`
If photo is a non-selected burst photo, returns a list of albums any other photos in the same burst set, are contained in. Otherwise, returns `PhotoInfo.albums`. If a burst photo which has unselected burst images (e.g. the burst images are in the library but haven't been selected by the user using the "Make a selection" feature) is placed in a an album, Photos treats only the selected "key" photo as in the album. The unselected burst images, while associated with the photo in the album, are not technically in the album. If you are handling one of these unselected burst photos and want to know which album it would be in based on which albums it's selected key images are in, use `burst_albums`. See also [burst_album_info](#burst_album_info) and [albums](#albums).
#### `burst_album_info`
If photo is non-selected burst photo, teturns a list of [AlbumInfo](#AlbumInfo) objects representing the albums any other photos in the same burst set are contained in. Otherwise, returns `PhotoInfo.album_info`. See also [burst_albums](#burst_albums) and [album_info](#album_info).
#### `live_photo`
Returns True if photo is an Apple live photo (ie. it has an associated "live" video component), otherwise returns False. See [path_live_photo](#path_live_photo).

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.41.4"
__version__ = "0.41.5"

View File

@@ -1408,6 +1408,9 @@ def export(
is_reference=is_reference,
in_album=in_album,
not_in_album=not_in_album,
burst_photos=export_bursts,
# skip missing bursts if using --download-missing by itself as AppleScript otherwise causes errors
missing_bursts=(download_missing and use_photokit) or not download_missing,
)
if photos:
@@ -1416,13 +1419,6 @@ def export(
previous_uuids = {uuid: 1 for uuid in export_db.get_previous_uuids()}
photos = [p for p in photos if p.uuid not in previous_uuids]
if export_bursts:
# add the burst_photos to the export set
photos_burst = [p for p in photos if p.burst]
for burst in photos_burst:
burst_set = [p for p in burst.burst_photos if not p.ismissing]
photos.extend(burst_set)
num_photos = len(photos)
# TODO: photos or photo appears several times, pull into a separate function
photo_str = "photos" if num_photos > 1 else "photo"
@@ -2019,6 +2015,8 @@ def _query(
is_reference=False,
in_album=False,
not_in_album=False,
burst_photos=None,
missing_bursts=None,
):
"""Run a query against PhotosDB to extract the photos based on user supply criteria used by query and export commands
@@ -2262,6 +2260,27 @@ def _query(
if to_time:
photos = [p for p in photos if p.date.time() <= to_time]
if burst_photos:
# add the burst_photos to the export set
photos_burst = [p for p in photos if p.burst]
for burst in photos_burst:
if missing_bursts:
# include burst photos that are missing
photos.extend(burst.burst_photos)
else:
# don't include missing burst images (these can't be downloaded with AppleScript)
photos.extend([p for p in burst.burst_photos if not p.ismissing])
# remove duplicates as each burst photo in the set that's selected would
# result in the entire set being added above
# can't use set() because PhotoInfo not hashable
seen_uuids = {}
for p in photos:
if p.uuid in seen_uuids:
continue
seen_uuids[p.uuid] = p
photos = list(seen_uuids.values())
return photos

View File

@@ -453,9 +453,24 @@ class PhotoInfo:
)
return self._albums
@property
def burst_albums(self):
"""If photo is non-selected burst photo, list of albums any other images in the same burst set are contained in, otherwise returns self.albums"""
if self.burst_selected or not self.burst:
return self.albums
try:
return self._burst_albums
except AttributeError:
burst_albums = []
for photo in self.burst_photos:
burst_albums.extend(photo.albums)
self._burst_albums = list(set(burst_albums))
return self._burst_albums
@property
def album_info(self):
""" list of AlbumInfo objects representing albums the photos is contained in """
""" list of AlbumInfo objects representing albums the photo is contained in """
try:
return self._album_info
except AttributeError:
@@ -465,6 +480,21 @@ class PhotoInfo:
]
return self._album_info
@property
def burst_album_info(self):
""" If photo is a non-selected burst photo, returns list of AlbumInfo objects representing albums any other photos in the same burst set are contained in, otherwise returns self.album_info """
if self.burst_selected or not self.burst:
return self.album_info
try:
return self._burst_album_info
except AttributeError:
burst_album_info = []
for photo in self.burst_photos:
burst_album_info.extend(photo.album_info)
self._burst_album_info = list(set(burst_album_info))
return self._burst_album_info
@property
def import_info(self):
""" ImportInfo object representing import session for the photo or None if no import session """
@@ -680,6 +710,11 @@ class PhotoInfo:
""" Returns True if photo is part of a Burst photo set, otherwise False """
return self._info["burst"]
@property
def burst_selected(self):
""" Returns True if photo is a burst photo and has been selected from the burst set by the user, otherwise False """
return self._info["burst_key"]
@property
def burst_photos(self):
"""If photo is a burst photo, returns list of PhotoInfo objects

View File

@@ -840,7 +840,7 @@ class PhotoTemplate:
""" return list of values for a multi-valued template field """
values = []
if field == "album":
values = self.photo.albums
values = self.photo.burst_albums if self.photo.burst else self.photo.albums
elif field == "keyword":
values = self.photo.keywords
elif field == "person":
@@ -854,7 +854,11 @@ class PhotoTemplate:
elif field == "folder_album":
values = []
# photos must be in an album to be in a folder
for album in self.photo.album_info:
if self.photo.burst:
album_info = self.photo.burst_album_info
else:
album_info = self.photo.album_info
for album in album_info:
if album.folder_names:
# album in folder
if dirname:

View File

@@ -181,7 +181,7 @@ pyobjc-framework-Vision==6.2.2
pyobjc-framework-WebKit==6.2.2
pyparsing==2.4.1.1
python-dateutil==2.8.1
PyYAML==5.1.2
PyYAML==5.4
pyzmq==18.1.1
readme-renderer==25.0
regex==2020.2.20

File diff suppressed because one or more lines are too long

View File

@@ -22,6 +22,17 @@ PHOTOS_DB_TOUCH = PHOTOS_DB_15_6
PHOTOS_DB_14_6 = "tests/Test-10.14.6.photoslibrary"
PHOTOS_DB_MOVIES = "tests/Test-Movie-5_0.photoslibrary"
# my personal library which some tests require
PHOTOS_DB_RHET = os.path.expanduser("~/Pictures/Photos Library.photoslibrary")
UUID_BURST_ALBUM = "9F90DC00-AAAF-4A05-9A65-61FEEE0D67F2" # in my personal library
BURST_ALBUM_FILES = [
"IMG_9812.JPG",
"IMG_9813.JPG",
"IMG_9814.JPG",
"IMG_9815.JPG",
"IMG_9816.JPG",
]
UUID_FILE = "tests/uuid_from_file.txt"
CLI_OUTPUT_NO_SUBCOMMAND = [
@@ -5649,3 +5660,42 @@ def test_export_jpeg_ext_convert_to_jpeg_movie():
assert f"{filename}.jpg".lower() not in files
assert f"{filename}.{ext}".lower() in files
assert f"{filename}_edited.{ext}".lower() in files
@pytest.mark.skipif(
"OSXPHOTOS_TEST_EXPORT" not in os.environ,
reason="Skip if not running on author's personal library.",
)
def test_export_burst_folder_album():
""" test non-selected burst photos are exported with the album their key photo is in, issue #401 """
import glob
import os
import os.path
import pathlib
from osxphotos.cli import export
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
export,
[
os.path.join(cwd, PHOTOS_DB_RHET),
".",
"-V",
"--directory",
"{folder_album}",
"--uuid",
UUID_BURST_ALBUM,
"--download-missing",
"--use-photokit",
],
)
assert result.exit_code == 0
folder_album = pathlib.Path("TestBurst")
assert folder_album.is_dir()
for filename in BURST_ALBUM_FILES:
path = folder_album / filename
assert path.is_file()

View File

@@ -17,6 +17,34 @@ UUID_DICT = {
"live": "BFF29EBD-22DF-4FCF-9817-317E7104EA50",
}
UUID_BURSTS = {
"9F90DC00-AAAF-4A05-9A65-61FEEE0D67F2": {
"selected": True,
"filename": "IMAGE_9812.JPG",
"albums": ["TestBurst"],
},
"964F457D-5FFC-47B9-BEAD-56B0A83FEF63": {
"selected": True,
"filename": "IMG_9816.JPG",
"albums": [],
},
"A385FA13-DF8E-482F-A8C5-970EDDF54C2F": {
"selected": False,
"filename": "IMG_9813.JPG",
"albums": ["TestBurst", "TestBurst2"],
},
"38F8F30C-FF6D-49DA-8092-18497F1D6628": {
"selected": True,
"filename": "IMG_9814.JPG",
"albums": ["TestBurst2"],
},
"E3863443-9EA8-417F-A90B-8F7086623DAD": {
"selected": False,
"filename": "IMG_9815.JPG",
"albums": ["TestBurst", "TestBurst2"],
},
}
@pytest.fixture(scope="module")
def photosdb():
@@ -156,3 +184,13 @@ def test_export_edited_no_edit(photosdb):
with pytest.raises(Exception) as e:
assert photos[0].export(dest, use_photos_export=True, edited=True)
assert e.type == ValueError
def test_burst_albums(photosdb):
"""Test burst_selected, burst_albums"""
for uuid in UUID_BURSTS:
photo = photosdb.get_photo(uuid)
assert photo.burst
assert photo.burst_selected == UUID_BURSTS[uuid]["selected"]
assert sorted(photo.burst_albums) == sorted(UUID_BURSTS[uuid]["albums"])